@intentius/chant 0.124.27 → 0.124.29

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 (681) hide show
  1. package/README.md +1 -1
  2. package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
  3. package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
  4. package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  6. package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
  7. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  8. package/dist/__golden__/wave-loop/op-held-on.json +2 -0
  9. package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
  10. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  11. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  12. package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
  13. package/dist/__golden__/wave-loop/op-per-run.json +7 -0
  14. package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
  15. package/dist/__golden__/wave-loop/op-sealed.json +3 -0
  16. package/dist/__golden__/wave-loop/op-spend.json +3 -0
  17. package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
  18. package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
  19. package/dist/build-params.d.ts +34 -1
  20. package/dist/build-params.d.ts.map +1 -1
  21. package/dist/build-params.js +31 -1
  22. package/dist/change-set-graph.d.ts +63 -0
  23. package/dist/change-set-graph.d.ts.map +1 -0
  24. package/dist/change-set-graph.js +130 -0
  25. package/dist/change-set.d.ts +62 -3
  26. package/dist/change-set.d.ts.map +1 -1
  27. package/dist/change-set.js +36 -4
  28. package/dist/chant-version.d.ts +9 -0
  29. package/dist/chant-version.d.ts.map +1 -0
  30. package/dist/chant-version.js +16 -0
  31. package/dist/cli/build-params-cli.d.ts +7 -1
  32. package/dist/cli/build-params-cli.d.ts.map +1 -1
  33. package/dist/cli/build-params-cli.js +17 -8
  34. package/dist/cli/commands/build.d.ts +8 -0
  35. package/dist/cli/commands/build.d.ts.map +1 -1
  36. package/dist/cli/commands/build.js +15 -11
  37. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  38. package/dist/cli/commands/check-lexicon-docs.js +30 -2
  39. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  40. package/dist/cli/commands/check-lexicon.js +13 -2
  41. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
  42. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  43. package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
  44. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  45. package/dist/cli/commands/init-lexicon.js +17 -8
  46. package/dist/cli/handlers/approve.d.ts.map +1 -1
  47. package/dist/cli/handlers/approve.js +64 -4
  48. package/dist/cli/handlers/build.d.ts.map +1 -1
  49. package/dist/cli/handlers/build.js +43 -4
  50. package/dist/cli/handlers/change-set.d.ts +10 -0
  51. package/dist/cli/handlers/change-set.d.ts.map +1 -1
  52. package/dist/cli/handlers/change-set.js +51 -1
  53. package/dist/cli/handlers/fan-out.d.ts +10 -1
  54. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  55. package/dist/cli/handlers/fan-out.js +54 -6
  56. package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
  57. package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
  58. package/dist/cli/handlers/lifecycle-gc.js +143 -0
  59. package/dist/cli/handlers/pr.d.ts +16 -2
  60. package/dist/cli/handlers/pr.d.ts.map +1 -1
  61. package/dist/cli/handlers/pr.js +269 -37
  62. package/dist/cli/handlers/promote.d.ts +7 -0
  63. package/dist/cli/handlers/promote.d.ts.map +1 -1
  64. package/dist/cli/handlers/promote.js +440 -28
  65. package/dist/cli/handlers/release.d.ts +91 -0
  66. package/dist/cli/handlers/release.d.ts.map +1 -0
  67. package/dist/cli/handlers/release.js +589 -0
  68. package/dist/cli/handlers/run.d.ts +38 -2
  69. package/dist/cli/handlers/run.d.ts.map +1 -1
  70. package/dist/cli/handlers/run.js +234 -16
  71. package/dist/cli/main.d.ts.map +1 -1
  72. package/dist/cli/main.js +143 -17
  73. package/dist/cli/mcp/workspace-tools.js +1 -1
  74. package/dist/cli/parse-args.d.ts.map +1 -1
  75. package/dist/cli/parse-args.js +73 -5
  76. package/dist/cli/registry.d.ts +43 -5
  77. package/dist/cli/registry.d.ts.map +1 -1
  78. package/dist/cli/run-events.d.ts +30 -0
  79. package/dist/cli/run-events.d.ts.map +1 -0
  80. package/dist/cli/run-events.js +49 -0
  81. package/dist/cli/version.d.ts +3 -5
  82. package/dist/cli/version.d.ts.map +1 -1
  83. package/dist/cli/version.js +3 -13
  84. package/dist/codegen/docs-in-site.d.ts +71 -0
  85. package/dist/codegen/docs-in-site.d.ts.map +1 -0
  86. package/dist/codegen/docs-in-site.js +177 -0
  87. package/dist/codegen/docs-types.d.ts +14 -1
  88. package/dist/codegen/docs-types.d.ts.map +1 -1
  89. package/dist/codegen/docs.d.ts +18 -1
  90. package/dist/codegen/docs.d.ts.map +1 -1
  91. package/dist/codegen/docs.js +40 -7
  92. package/dist/codegen/rehype-base-url.mjs +15 -9
  93. package/dist/components/auto-release.d.ts +6 -1
  94. package/dist/components/auto-release.d.ts.map +1 -1
  95. package/dist/components/auto-release.js +3 -0
  96. package/dist/components/capability.d.ts +9 -0
  97. package/dist/components/capability.d.ts.map +1 -1
  98. package/dist/components/cli-support.d.ts +6 -0
  99. package/dist/components/cli-support.d.ts.map +1 -1
  100. package/dist/components/cli-support.js +10 -9
  101. package/dist/components/driver.d.ts +50 -9
  102. package/dist/components/driver.d.ts.map +1 -1
  103. package/dist/components/driver.js +116 -54
  104. package/dist/components/fan-out-inputs.d.ts +69 -0
  105. package/dist/components/fan-out-inputs.d.ts.map +1 -0
  106. package/dist/components/fan-out-inputs.js +169 -0
  107. package/dist/components/fan-out-record.d.ts +13 -0
  108. package/dist/components/fan-out-record.d.ts.map +1 -1
  109. package/dist/components/fan-out-record.js +16 -0
  110. package/dist/components/fan-out-run.d.ts +7 -0
  111. package/dist/components/fan-out-run.d.ts.map +1 -1
  112. package/dist/components/fan-out-run.js +85 -26
  113. package/dist/components/fan-out.d.ts +4 -0
  114. package/dist/components/fan-out.d.ts.map +1 -1
  115. package/dist/components/pr-pipeline.d.ts +25 -0
  116. package/dist/components/pr-pipeline.d.ts.map +1 -1
  117. package/dist/components/pr-pipeline.js +22 -4
  118. package/dist/components/pr-run.d.ts +11 -0
  119. package/dist/components/pr-run.d.ts.map +1 -1
  120. package/dist/components/pr-run.js +20 -1
  121. package/dist/components/promote-order.d.ts +38 -0
  122. package/dist/components/promote-order.d.ts.map +1 -0
  123. package/dist/components/promote-order.js +62 -0
  124. package/dist/components/promote.d.ts +38 -2
  125. package/dist/components/promote.d.ts.map +1 -1
  126. package/dist/components/promote.js +77 -2
  127. package/dist/components/release-compile.d.ts +142 -0
  128. package/dist/components/release-compile.d.ts.map +1 -0
  129. package/dist/components/release-compile.js +382 -0
  130. package/dist/components/release-gate.d.ts +135 -0
  131. package/dist/components/release-gate.d.ts.map +1 -0
  132. package/dist/components/release-gate.js +288 -0
  133. package/dist/components/release-promote.d.ts +96 -0
  134. package/dist/components/release-promote.d.ts.map +1 -0
  135. package/dist/components/release-promote.js +204 -0
  136. package/dist/components/run-progress.d.ts +7 -0
  137. package/dist/components/run-progress.d.ts.map +1 -1
  138. package/dist/components/wave-plan.d.ts +8 -1
  139. package/dist/components/wave-plan.d.ts.map +1 -1
  140. package/dist/components/wave-plan.js +11 -3
  141. package/dist/config.d.ts +45 -0
  142. package/dist/config.d.ts.map +1 -1
  143. package/dist/config.js +3 -0
  144. package/dist/flag-params.d.ts +54 -0
  145. package/dist/flag-params.d.ts.map +1 -0
  146. package/dist/flag-params.js +130 -0
  147. package/dist/index.d.ts +1 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +2 -0
  150. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  151. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  152. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  153. package/dist/lifecycle/change-set-store.d.ts +68 -0
  154. package/dist/lifecycle/change-set-store.d.ts.map +1 -0
  155. package/dist/lifecycle/change-set-store.js +97 -0
  156. package/dist/lifecycle/commit-sha.d.ts +13 -0
  157. package/dist/lifecycle/commit-sha.d.ts.map +1 -0
  158. package/dist/lifecycle/commit-sha.js +21 -0
  159. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  160. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  161. package/dist/lifecycle/delivery-metrics.js +145 -0
  162. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  163. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  164. package/dist/lifecycle/deploy-outcome.js +101 -0
  165. package/dist/lifecycle/gate-change-set.d.ts +78 -0
  166. package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
  167. package/dist/lifecycle/gate-change-set.js +138 -0
  168. package/dist/lifecycle/gate-ledger.d.ts +51 -4
  169. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  170. package/dist/lifecycle/gate-ledger.js +18 -0
  171. package/dist/lifecycle/git.d.ts +30 -1
  172. package/dist/lifecycle/git.d.ts.map +1 -1
  173. package/dist/lifecycle/git.js +31 -5
  174. package/dist/lifecycle/index.d.ts +4 -0
  175. package/dist/lifecycle/index.d.ts.map +1 -1
  176. package/dist/lifecycle/index.js +4 -0
  177. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  178. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  179. package/dist/lifecycle/mixed-environment.js +154 -0
  180. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  181. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  182. package/dist/lifecycle/publish-attachments.js +179 -0
  183. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  184. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  185. package/dist/lifecycle/publish-credentials.js +69 -0
  186. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  187. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  188. package/dist/lifecycle/publish-declaration.js +83 -0
  189. package/dist/lifecycle/publish-forge.d.ts +53 -0
  190. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  191. package/dist/lifecycle/publish-forge.js +298 -0
  192. package/dist/lifecycle/publish-npm.d.ts +61 -0
  193. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  194. package/dist/lifecycle/publish-npm.js +242 -0
  195. package/dist/lifecycle/publish-oci.d.ts +64 -0
  196. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  197. package/dist/lifecycle/publish-oci.js +383 -0
  198. package/dist/lifecycle/publish-record.d.ts +66 -0
  199. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  200. package/dist/lifecycle/publish-record.js +84 -0
  201. package/dist/lifecycle/publish-targets.d.ts +45 -0
  202. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  203. package/dist/lifecycle/publish-targets.js +85 -0
  204. package/dist/lifecycle/release-cut.d.ts +154 -0
  205. package/dist/lifecycle/release-cut.d.ts.map +1 -0
  206. package/dist/lifecycle/release-cut.js +366 -0
  207. package/dist/lifecycle/release-join.d.ts +95 -0
  208. package/dist/lifecycle/release-join.d.ts.map +1 -0
  209. package/dist/lifecycle/release-join.js +139 -0
  210. package/dist/lifecycle/release-ledger.d.ts +54 -0
  211. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  212. package/dist/lifecycle/release-ledger.js +53 -20
  213. package/dist/lifecycle/release-publish.d.ts +312 -0
  214. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  215. package/dist/lifecycle/release-publish.js +335 -0
  216. package/dist/lifecycle/release-revocation.d.ts +80 -0
  217. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  218. package/dist/lifecycle/release-revocation.js +123 -0
  219. package/dist/lifecycle/release-store.d.ts +200 -0
  220. package/dist/lifecycle/release-store.d.ts.map +1 -0
  221. package/dist/lifecycle/release-store.js +320 -0
  222. package/dist/lifecycle/release-verify.d.ts +61 -0
  223. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  224. package/dist/lifecycle/release-verify.js +92 -0
  225. package/dist/lifecycle/release-workstation.d.ts +91 -0
  226. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  227. package/dist/lifecycle/release-workstation.js +180 -0
  228. package/dist/lifecycle/retention-policy.d.ts +45 -0
  229. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  230. package/dist/lifecycle/retention-policy.js +43 -0
  231. package/dist/lifecycle/retention.d.ts +185 -0
  232. package/dist/lifecycle/retention.d.ts.map +1 -0
  233. package/dist/lifecycle/retention.js +400 -0
  234. package/dist/lifecycle/run-ledger.d.ts +4 -0
  235. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  236. package/dist/lifecycle/run-ledger.js +2 -0
  237. package/dist/op/activities/change-coverage.d.ts.map +1 -1
  238. package/dist/op/activities/change-coverage.js +4 -2
  239. package/dist/op/activities/source-release.d.ts.map +1 -1
  240. package/dist/op/activities/source-release.js +7 -3
  241. package/dist/op/activities/source-rollback.d.ts.map +1 -1
  242. package/dist/op/activities/source-rollback.js +7 -3
  243. package/dist/op/gate-name.d.ts +11 -2
  244. package/dist/op/gate-name.d.ts.map +1 -1
  245. package/dist/op/gate-name.js +12 -3
  246. package/dist/op/gate-run-locator.d.ts +22 -0
  247. package/dist/op/gate-run-locator.d.ts.map +1 -1
  248. package/dist/op/gate-run-locator.js +31 -0
  249. package/dist/op/gate.d.ts +27 -3
  250. package/dist/op/gate.d.ts.map +1 -1
  251. package/dist/op/gate.js +57 -3
  252. package/dist/op/local-executor.d.ts +36 -1
  253. package/dist/op/local-executor.d.ts.map +1 -1
  254. package/dist/op/local-executor.js +158 -52
  255. package/dist/op/op-waves-run.d.ts.map +1 -1
  256. package/dist/op/op-waves-run.js +4 -0
  257. package/dist/op/run-live.d.ts +2 -0
  258. package/dist/op/run-live.d.ts.map +1 -1
  259. package/dist/op/run-live.js +1 -1
  260. package/dist/op/runtime.d.ts +46 -0
  261. package/dist/op/runtime.d.ts.map +1 -1
  262. package/dist/op/runtimes/local.d.ts.map +1 -1
  263. package/dist/op/runtimes/local.js +8 -0
  264. package/dist/plan-summary.d.ts.map +1 -1
  265. package/dist/plan-summary.js +8 -2
  266. package/dist/point-decision.d.ts +2 -0
  267. package/dist/point-decision.d.ts.map +1 -1
  268. package/dist/point-decision.js +9 -2
  269. package/dist/pr-loop.d.ts +7 -0
  270. package/dist/pr-loop.d.ts.map +1 -1
  271. package/dist/pr-loop.js +23 -4
  272. package/dist/pr-report-url.d.ts +82 -0
  273. package/dist/pr-report-url.d.ts.map +1 -0
  274. package/dist/pr-report-url.js +174 -0
  275. package/dist/provenance.d.ts +2 -2
  276. package/dist/provenance.d.ts.map +1 -1
  277. package/dist/run-event-store.d.ts +104 -0
  278. package/dist/run-event-store.d.ts.map +1 -0
  279. package/dist/run-event-store.js +251 -0
  280. package/dist/run-event-stream.d.ts +133 -0
  281. package/dist/run-event-stream.d.ts.map +1 -0
  282. package/dist/run-event-stream.js +242 -0
  283. package/dist/run-event.d.ts +285 -0
  284. package/dist/run-event.d.ts.map +1 -0
  285. package/dist/run-event.js +209 -0
  286. package/dist/wave-runtime.mjs +262 -226
  287. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  288. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  289. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  290. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  291. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  292. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  293. package/dist/workspace/change-set-show.d.ts +56 -0
  294. package/dist/workspace/change-set-show.d.ts.map +1 -0
  295. package/dist/workspace/change-set-show.js +54 -0
  296. package/dist/workspace/change-set-show.schema.json +102 -0
  297. package/dist/workspace/change-set.schema.json +32 -1
  298. package/dist/workspace/changes-cli.d.ts.map +1 -1
  299. package/dist/workspace/changes-cli.js +10 -1
  300. package/dist/workspace/changes.d.ts +37 -0
  301. package/dist/workspace/changes.d.ts.map +1 -1
  302. package/dist/workspace/changes.js +49 -2
  303. package/dist/workspace/changes.schema.json +135 -0
  304. package/dist/workspace/check.schema.json +4 -2
  305. package/dist/workspace/checks/records.d.ts +9 -1
  306. package/dist/workspace/checks/records.d.ts.map +1 -1
  307. package/dist/workspace/checks/records.js +35 -1
  308. package/dist/workspace/checks.d.ts +1 -0
  309. package/dist/workspace/checks.d.ts.map +1 -1
  310. package/dist/workspace/checks.js +2 -1
  311. package/dist/workspace/ci-green-cli.d.ts +19 -4
  312. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  313. package/dist/workspace/ci-green-cli.js +75 -12
  314. package/dist/workspace/ci-green-forge.d.ts +20 -3
  315. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  316. package/dist/workspace/ci-green-forge.js +6 -4
  317. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  318. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  319. package/dist/workspace/ci-green-workflow.js +6 -0
  320. package/dist/workspace/ci-green.d.ts +281 -13
  321. package/dist/workspace/ci-green.d.ts.map +1 -1
  322. package/dist/workspace/ci-green.js +456 -38
  323. package/dist/workspace/ci-last-green.schema.json +32 -2
  324. package/dist/workspace/ci-list.schema.json +525 -0
  325. package/dist/workspace/commit-links.d.ts +85 -0
  326. package/dist/workspace/commit-links.d.ts.map +1 -0
  327. package/dist/workspace/commit-links.js +163 -0
  328. package/dist/workspace/declaration.d.ts +35 -0
  329. package/dist/workspace/declaration.d.ts.map +1 -1
  330. package/dist/workspace/declaration.js +27 -1
  331. package/dist/workspace/declaration.schema.json +76 -0
  332. package/dist/workspace/declared-kinds.d.ts +16 -0
  333. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  334. package/dist/workspace/declared-kinds.js +25 -0
  335. package/dist/workspace/delivery-metrics.d.ts +91 -0
  336. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  337. package/dist/workspace/delivery-metrics.js +217 -0
  338. package/dist/workspace/delivery-metrics.schema.json +519 -0
  339. package/dist/workspace/flags-read.d.ts +37 -0
  340. package/dist/workspace/flags-read.d.ts.map +1 -0
  341. package/dist/workspace/flags-read.js +103 -0
  342. package/dist/workspace/flags.d.ts +100 -0
  343. package/dist/workspace/flags.d.ts.map +1 -0
  344. package/dist/workspace/flags.js +98 -0
  345. package/dist/workspace/graph-cli.js +1 -1
  346. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  347. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  348. package/dist/workspace/identity-gate-rule.js +72 -7
  349. package/dist/workspace/identity.d.ts +12 -0
  350. package/dist/workspace/identity.d.ts.map +1 -1
  351. package/dist/workspace/identity.js +13 -1
  352. package/dist/workspace/intent-cli.d.ts +11 -2
  353. package/dist/workspace/intent-cli.d.ts.map +1 -1
  354. package/dist/workspace/intent-cli.js +54 -5
  355. package/dist/workspace/intent-trace.d.ts +219 -0
  356. package/dist/workspace/intent-trace.d.ts.map +1 -0
  357. package/dist/workspace/intent-trace.js +314 -0
  358. package/dist/workspace/intent.d.ts +36 -7
  359. package/dist/workspace/intent.d.ts.map +1 -1
  360. package/dist/workspace/intent.js +192 -22
  361. package/dist/workspace/intent.schema.json +702 -6
  362. package/dist/workspace/ls.d.ts +4 -0
  363. package/dist/workspace/ls.d.ts.map +1 -1
  364. package/dist/workspace/ls.js +2 -0
  365. package/dist/workspace/ls.schema.json +3 -1
  366. package/dist/workspace/member-pipeline.d.ts +6 -0
  367. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  368. package/dist/workspace/member-pipeline.js +6 -0
  369. package/dist/workspace/overlap.d.ts +146 -0
  370. package/dist/workspace/overlap.d.ts.map +1 -0
  371. package/dist/workspace/overlap.js +276 -0
  372. package/dist/workspace/promotion-order.d.ts +41 -0
  373. package/dist/workspace/promotion-order.d.ts.map +1 -0
  374. package/dist/workspace/promotion-order.js +95 -0
  375. package/dist/workspace/reason-codes.d.ts +4 -0
  376. package/dist/workspace/reason-codes.d.ts.map +1 -1
  377. package/dist/workspace/reason-codes.js +7 -1
  378. package/dist/workspace/records.d.ts +15 -2
  379. package/dist/workspace/records.d.ts.map +1 -1
  380. package/dist/workspace/records.js +32 -0
  381. package/dist/workspace/records.schema.json +21 -2
  382. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  383. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  384. package/dist/workspace/releases.d.ts +81 -0
  385. package/dist/workspace/releases.d.ts.map +1 -0
  386. package/dist/workspace/releases.js +103 -0
  387. package/dist/workspace/releases.schema.json +513 -0
  388. package/dist/workspace/run-event.schema.json +295 -0
  389. package/dist/workspace/status-gates.d.ts +34 -1
  390. package/dist/workspace/status-gates.d.ts.map +1 -1
  391. package/dist/workspace/status-gates.js +31 -2
  392. package/dist/workspace/status-stewards.d.ts +4 -0
  393. package/dist/workspace/status-stewards.d.ts.map +1 -1
  394. package/dist/workspace/status-stewards.js +2 -0
  395. package/dist/workspace/status.d.ts +117 -0
  396. package/dist/workspace/status.d.ts.map +1 -1
  397. package/dist/workspace/status.js +218 -4
  398. package/dist/workspace/status.schema.json +268 -2
  399. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  400. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  401. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  402. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  403. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  404. package/dist/workspace/trust/op-run-statement.js +143 -0
  405. package/dist/workspace/trust/rotation.d.ts +18 -2
  406. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  407. package/dist/workspace/trust/rotation.js +65 -16
  408. package/dist/workspace/trust/seal.d.ts +11 -0
  409. package/dist/workspace/trust/seal.d.ts.map +1 -1
  410. package/dist/workspace/trust/seal.js +9 -0
  411. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  412. package/dist/workspace/trust/ssh-commit.js +10 -10
  413. package/dist/workspace/wip.d.ts.map +1 -1
  414. package/dist/workspace/wip.js +11 -3
  415. package/package.json +1 -1
  416. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  417. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  418. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  419. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  420. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  421. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  422. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  423. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  424. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  425. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  426. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  427. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  428. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  429. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  430. package/src/__golden__/wave-loop/op-spend.json +3 -0
  431. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  432. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  433. package/src/build-params.test.ts +46 -0
  434. package/src/build-params.ts +55 -1
  435. package/src/change-set-graph.test.ts +72 -0
  436. package/src/change-set-graph.ts +141 -0
  437. package/src/change-set.ts +87 -6
  438. package/src/chant-version.ts +16 -0
  439. package/src/cli/build-params-cli.ts +22 -9
  440. package/src/cli/commands/build.ts +25 -11
  441. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  442. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  443. package/src/cli/commands/check-lexicon.ts +14 -2
  444. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  445. package/src/cli/commands/init-lexicon.test.ts +25 -0
  446. package/src/cli/commands/init-lexicon.ts +18 -8
  447. package/src/cli/handlers/approve-release.test.ts +84 -0
  448. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  449. package/src/cli/handlers/approve.ts +65 -2
  450. package/src/cli/handlers/build.test.ts +67 -0
  451. package/src/cli/handlers/build.ts +46 -4
  452. package/src/cli/handlers/change-set.ts +49 -1
  453. package/src/cli/handlers/fan-out.test.ts +228 -1
  454. package/src/cli/handlers/fan-out.ts +53 -6
  455. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  456. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  457. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  458. package/src/cli/handlers/operator.test.ts +37 -0
  459. package/src/cli/handlers/pr.test.ts +341 -0
  460. package/src/cli/handlers/pr.ts +292 -47
  461. package/src/cli/handlers/promote.test.ts +669 -7
  462. package/src/cli/handlers/promote.ts +464 -29
  463. package/src/cli/handlers/release-implicit.e2e.test.ts +161 -0
  464. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  465. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  466. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  467. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  468. package/src/cli/handlers/release.e2e.test.ts +199 -0
  469. package/src/cli/handlers/release.ts +593 -0
  470. package/src/cli/handlers/run-auto-release.test.ts +80 -0
  471. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  472. package/src/cli/handlers/run-release.test.ts +55 -0
  473. package/src/cli/handlers/run.test.ts +30 -0
  474. package/src/cli/handlers/run.ts +243 -18
  475. package/src/cli/main.test.ts +8 -0
  476. package/src/cli/main.ts +143 -17
  477. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  478. package/src/cli/mcp/workspace-tools.ts +1 -1
  479. package/src/cli/parse-args.ts +56 -5
  480. package/src/cli/registry.ts +43 -5
  481. package/src/cli/run-events.ts +57 -0
  482. package/src/cli/version.ts +3 -13
  483. package/src/codegen/docs-in-site.test.ts +166 -0
  484. package/src/codegen/docs-in-site.ts +182 -0
  485. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  486. package/src/codegen/docs-types.ts +14 -1
  487. package/src/codegen/docs.ts +60 -7
  488. package/src/codegen/rehype-base-url.d.mts +3 -1
  489. package/src/codegen/rehype-base-url.mjs +15 -9
  490. package/src/codegen/rehype-base-url.test.ts +34 -0
  491. package/src/components/auto-release.test.ts +30 -0
  492. package/src/components/auto-release.ts +9 -1
  493. package/src/components/capability.ts +9 -0
  494. package/src/components/cli-support.test.ts +82 -0
  495. package/src/components/cli-support.ts +16 -9
  496. package/src/components/driver.test.ts +24 -0
  497. package/src/components/driver.ts +165 -56
  498. package/src/components/fan-out-inputs.test.ts +85 -0
  499. package/src/components/fan-out-inputs.ts +192 -0
  500. package/src/components/fan-out-record.test.ts +10 -0
  501. package/src/components/fan-out-record.ts +19 -0
  502. package/src/components/fan-out-run.ts +104 -35
  503. package/src/components/fan-out.ts +4 -0
  504. package/src/components/pr-pipeline.ts +35 -4
  505. package/src/components/pr-run.ts +30 -1
  506. package/src/components/promote-order.test.ts +90 -0
  507. package/src/components/promote-order.ts +75 -0
  508. package/src/components/promote.test.ts +10 -1
  509. package/src/components/promote.ts +94 -3
  510. package/src/components/release-compile.test.ts +147 -0
  511. package/src/components/release-compile.ts +459 -0
  512. package/src/components/release-gate.test.ts +215 -0
  513. package/src/components/release-gate.ts +355 -0
  514. package/src/components/release-promote.test.ts +159 -0
  515. package/src/components/release-promote.ts +232 -0
  516. package/src/components/run-progress.ts +7 -0
  517. package/src/components/wave-plan.ts +16 -3
  518. package/src/config.ts +15 -1
  519. package/src/flag-params.test.ts +144 -0
  520. package/src/flag-params.ts +134 -0
  521. package/src/index.ts +2 -0
  522. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  523. package/src/lifecycle/change-set-store.test.ts +217 -0
  524. package/src/lifecycle/change-set-store.ts +113 -0
  525. package/src/lifecycle/commit-sha.ts +23 -0
  526. package/src/lifecycle/delivery-metrics.ts +263 -0
  527. package/src/lifecycle/deploy-outcome.ts +147 -0
  528. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  529. package/src/lifecycle/gate-change-set.ts +164 -0
  530. package/src/lifecycle/gate-ledger.test.ts +33 -0
  531. package/src/lifecycle/gate-ledger.ts +68 -3
  532. package/src/lifecycle/git.ts +61 -8
  533. package/src/lifecycle/index.ts +4 -0
  534. package/src/lifecycle/mixed-environment.test.ts +97 -0
  535. package/src/lifecycle/mixed-environment.ts +204 -0
  536. package/src/lifecycle/publish-attachments.ts +196 -0
  537. package/src/lifecycle/publish-credentials.ts +84 -0
  538. package/src/lifecycle/publish-declaration.ts +95 -0
  539. package/src/lifecycle/publish-forge.ts +325 -0
  540. package/src/lifecycle/publish-npm.ts +259 -0
  541. package/src/lifecycle/publish-oci.ts +396 -0
  542. package/src/lifecycle/publish-record.ts +113 -0
  543. package/src/lifecycle/publish-resume.test.ts +168 -0
  544. package/src/lifecycle/publish-targets.test.ts +494 -0
  545. package/src/lifecycle/publish-targets.ts +100 -0
  546. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  547. package/src/lifecycle/release-cut.test.ts +483 -0
  548. package/src/lifecycle/release-cut.ts +503 -0
  549. package/src/lifecycle/release-flag.test.ts +112 -0
  550. package/src/lifecycle/release-join.test.ts +68 -0
  551. package/src/lifecycle/release-join.ts +182 -0
  552. package/src/lifecycle/release-ledger.test.ts +14 -0
  553. package/src/lifecycle/release-ledger.ts +85 -17
  554. package/src/lifecycle/release-publish.test.ts +225 -0
  555. package/src/lifecycle/release-publish.ts +568 -0
  556. package/src/lifecycle/release-revocation.ts +147 -0
  557. package/src/lifecycle/release-store.ts +434 -0
  558. package/src/lifecycle/release-verify.test.ts +110 -0
  559. package/src/lifecycle/release-verify.ts +120 -0
  560. package/src/lifecycle/release-workstation.test.ts +204 -0
  561. package/src/lifecycle/release-workstation.ts +216 -0
  562. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  563. package/src/lifecycle/retention-policy.ts +63 -0
  564. package/src/lifecycle/retention.test.ts +363 -0
  565. package/src/lifecycle/retention.ts +508 -0
  566. package/src/lifecycle/run-ledger.test.ts +10 -0
  567. package/src/lifecycle/run-ledger.ts +6 -0
  568. package/src/op/activities/change-coverage.ts +4 -1
  569. package/src/op/activities/source-release.ts +8 -3
  570. package/src/op/activities/source-rollback.ts +8 -4
  571. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  572. package/src/op/gate-name.ts +13 -3
  573. package/src/op/gate-release.test.ts +126 -0
  574. package/src/op/gate-run-locator.test.ts +110 -0
  575. package/src/op/gate-run-locator.ts +35 -0
  576. package/src/op/gate.ts +88 -6
  577. package/src/op/local-executor.ts +207 -52
  578. package/src/op/op-waves-run.ts +4 -0
  579. package/src/op/run-events.test.ts +166 -0
  580. package/src/op/run-live.ts +1 -1
  581. package/src/op/runtime.ts +39 -0
  582. package/src/op/runtimes/local.test.ts +16 -0
  583. package/src/op/runtimes/local.ts +9 -0
  584. package/src/plan-summary.test.ts +20 -0
  585. package/src/plan-summary.ts +8 -1
  586. package/src/point-decision.ts +12 -2
  587. package/src/pr-loop.ts +25 -4
  588. package/src/pr-report-url.test.ts +131 -0
  589. package/src/pr-report-url.ts +209 -0
  590. package/src/provenance.ts +2 -2
  591. package/src/run-event-store.test.ts +144 -0
  592. package/src/run-event-store.ts +273 -0
  593. package/src/run-event-stream.test.ts +175 -0
  594. package/src/run-event-stream.ts +309 -0
  595. package/src/run-event.test.ts +277 -0
  596. package/src/run-event.ts +471 -0
  597. package/src/wave-loop-golden.test.ts +9 -0
  598. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  599. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  600. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  601. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  602. package/src/workspace/ancestry-index.test.ts +93 -0
  603. package/src/workspace/change-set-show.schema.json +102 -0
  604. package/src/workspace/change-set-show.ts +72 -0
  605. package/src/workspace/change-set.schema.json +32 -1
  606. package/src/workspace/changes-cli.ts +10 -1
  607. package/src/workspace/changes.schema.json +135 -0
  608. package/src/workspace/changes.test.ts +1 -1
  609. package/src/workspace/changes.ts +84 -2
  610. package/src/workspace/check.schema.json +4 -2
  611. package/src/workspace/checks/records.ts +35 -1
  612. package/src/workspace/checks.ts +2 -1
  613. package/src/workspace/ci-green-cli.ts +75 -13
  614. package/src/workspace/ci-green-forge.ts +24 -4
  615. package/src/workspace/ci-green-runs.test.ts +242 -0
  616. package/src/workspace/ci-green-signed.test.ts +302 -0
  617. package/src/workspace/ci-green-workflow.test.ts +16 -0
  618. package/src/workspace/ci-green-workflow.ts +12 -0
  619. package/src/workspace/ci-green.test.ts +131 -2
  620. package/src/workspace/ci-green.ts +635 -42
  621. package/src/workspace/ci-last-green.schema.json +32 -2
  622. package/src/workspace/ci-list.schema.json +525 -0
  623. package/src/workspace/commit-links.test.ts +279 -0
  624. package/src/workspace/commit-links.ts +183 -0
  625. package/src/workspace/declaration.schema.json +76 -0
  626. package/src/workspace/declaration.test.ts +28 -0
  627. package/src/workspace/declaration.ts +60 -2
  628. package/src/workspace/declared-kinds.ts +40 -1
  629. package/src/workspace/delivery-metrics.schema.json +519 -0
  630. package/src/workspace/delivery-metrics.test.ts +236 -0
  631. package/src/workspace/delivery-metrics.ts +286 -0
  632. package/src/workspace/flags-read.ts +121 -0
  633. package/src/workspace/flags.test.ts +141 -0
  634. package/src/workspace/flags.ts +157 -0
  635. package/src/workspace/graph-cli.ts +1 -1
  636. package/src/workspace/identity-gate-rule.ts +91 -7
  637. package/src/workspace/identity.ts +14 -1
  638. package/src/workspace/intent-cli.ts +46 -5
  639. package/src/workspace/intent-trace.test.ts +173 -0
  640. package/src/workspace/intent-trace.ts +484 -0
  641. package/src/workspace/intent.schema.json +702 -6
  642. package/src/workspace/intent.ts +196 -26
  643. package/src/workspace/ls.schema.json +3 -1
  644. package/src/workspace/ls.ts +6 -0
  645. package/src/workspace/member-pipeline.test.ts +17 -0
  646. package/src/workspace/member-pipeline.ts +9 -0
  647. package/src/workspace/older-records.test.ts +412 -0
  648. package/src/workspace/overlap.test.ts +165 -0
  649. package/src/workspace/overlap.ts +340 -0
  650. package/src/workspace/promotion-order.ts +97 -0
  651. package/src/workspace/read-contract.e2e.test.ts +5 -1
  652. package/src/workspace/reason-codes.test.ts +7 -1
  653. package/src/workspace/reason-codes.ts +7 -1
  654. package/src/workspace/record-assets.test.ts +2 -1
  655. package/src/workspace/records-contract.e2e.test.ts +2 -1
  656. package/src/workspace/records.schema.json +21 -2
  657. package/src/workspace/records.ts +36 -2
  658. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  659. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  660. package/src/workspace/releases.schema.json +513 -0
  661. package/src/workspace/releases.test.ts +108 -0
  662. package/src/workspace/releases.ts +133 -0
  663. package/src/workspace/run-event.schema.json +295 -0
  664. package/src/workspace/status-contract.test.ts +81 -1
  665. package/src/workspace/status-gates.ts +64 -1
  666. package/src/workspace/status-mixed.test.ts +152 -0
  667. package/src/workspace/status-revoked.test.ts +153 -0
  668. package/src/workspace/status-stewards.ts +6 -0
  669. package/src/workspace/status.schema.json +268 -2
  670. package/src/workspace/status.ts +334 -4
  671. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  672. package/src/workspace/trust/op-run-statement.ts +148 -0
  673. package/src/workspace/trust/rotation.ts +61 -14
  674. package/src/workspace/trust/seal.ts +10 -0
  675. package/src/workspace/trust/ssh-commit.ts +10 -9
  676. package/src/workspace/wip.ts +11 -2
  677. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  678. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  679. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  680. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  681. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -0,0 +1,209 @@
1
+ /**
2
+ * `pr-plan --report-url` and `pr-apply --report-url` (#4007): copy a stage's
3
+ * report, its change set and its run events where a reader outside CI can
4
+ * fetch them, keyed by pull request and commit.
5
+ *
6
+ * ```
7
+ * <url>/<pr>/<commit>/pr-<stage>.json
8
+ * <url>/<pr>/<commit>/change-set.json
9
+ * <url>/<pr>/<commit>/pr-<stage>.events.jsonl (with --events-json)
10
+ * ```
11
+ *
12
+ * `<pr>` is the pull or merge request number, or `unknown` when pr-apply
13
+ * found none, and `<commit>` is the full sha the stage ran on: the pull
14
+ * request's head for pr-plan, the pushed commit for pr-apply. A workspace
15
+ * member's loop (`--member`) adds `/<member>` after the commit. There is no
16
+ * index: a reader that knows the pull request and the commit knows the key.
17
+ * The events file is named for its stage because pr-plan and pr-apply run on
18
+ * the same commit when a pull request merges by fast-forward.
19
+ *
20
+ * The events file is uploaded again, whole, each time a wave, a component or
21
+ * the run settles, so a reader polling the URL sees each wave as it applies.
22
+ * One upload of it is in flight at a time: a settle while one is in flight
23
+ * asks for one more after it, with everything written by then. A failed
24
+ * upload is a warning and the run goes on. The last upload, after the run
25
+ * settles, carries the whole stream, and the report and the change set go
26
+ * with it.
27
+ *
28
+ * Two schemes. `file://<dir>` writes under a directory, each file through a
29
+ * temporary name and a rename, so a reader never sees half a file. `s3://
30
+ * <bucket>/<prefix>` runs the AWS CLI (`aws s3 cp - <s3 url>`, the body on
31
+ * its stdin), which reads its credentials, region and endpoint from the
32
+ * environment the job gives it, the way the aws lexicon's `s3-sync` does.
33
+ * chant passes it no credential and prints none: a failed upload prints the
34
+ * CLI's own error line.
35
+ */
36
+
37
+ import { spawn } from "node:child_process";
38
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
39
+ import { dirname, isAbsolute, join } from "node:path";
40
+ import { fileURLToPath } from "node:url";
41
+
42
+ /** Where `--report-url` points. */
43
+ export type ReportTarget =
44
+ | { scheme: "file"; url: string; dir: string }
45
+ | { scheme: "s3"; url: string; bucket: string; prefix: string };
46
+
47
+ /** `--report-url` read, or why it can't be. */
48
+ export function parseReportUrl(raw: string): ReportTarget | { error: string } {
49
+ const url = raw.trim();
50
+ if (url.startsWith("file://")) {
51
+ let dir: string;
52
+ try {
53
+ dir = fileURLToPath(url);
54
+ } catch (err) {
55
+ return { error: `--report-url ${url}: ${err instanceof Error ? err.message : String(err)}` };
56
+ }
57
+ if (!isAbsolute(dir)) return { error: `--report-url ${url}: a file URL names an absolute directory` };
58
+ return { scheme: "file", url: url.replace(/\/+$/, ""), dir };
59
+ }
60
+ const s3 = /^s3:\/\/([^/]+)\/?(.*)$/.exec(url);
61
+ if (s3) {
62
+ const prefix = s3[2]!.replace(/\/+$/, "");
63
+ return { scheme: "s3", url: `s3://${s3[1]}${prefix ? `/${prefix}` : ""}`, bucket: s3[1]!, prefix };
64
+ }
65
+ return { error: `--report-url ${url}: takes an s3://<bucket>/<prefix> or file://<directory> URL` };
66
+ }
67
+
68
+ /** The key a stage's files are kept under, below the URL: `<pr>/<commit>[/<member>]`. */
69
+ export function reportKey(pr: number | null, commit: string, member?: string): string {
70
+ return [pr === null ? "unknown" : String(pr), commit, ...(member ? [member] : [])].join("/");
71
+ }
72
+
73
+ /** The events file of a stage. */
74
+ export const reportEventsFile = (stage: "plan" | "apply"): string => `pr-${stage}.events.jsonl`;
75
+
76
+ /** Puts one object: `key` below the URL. Rejects with a line a person can read. */
77
+ export type ReportPut = (key: string, body: string, contentType: string) => Promise<void>;
78
+
79
+ /** Runs the AWS CLI with `args` and `stdin`, rejecting with its error line on a non-zero exit. */
80
+ export type AwsCli = (args: string[], stdin: string) => Promise<void>;
81
+
82
+ const AWS_BINARY = "aws";
83
+
84
+ /** The AWS CLI on PATH. */
85
+ export const realAwsCli: AwsCli = (args, stdin) =>
86
+ new Promise((resolvePut, reject) => {
87
+ const child = spawn(AWS_BINARY, args, { stdio: ["pipe", "ignore", "pipe"] });
88
+ let stderr = "";
89
+ child.stderr.on("data", (chunk: Buffer) => {
90
+ if (stderr.length < 4096) stderr += chunk.toString("utf8");
91
+ });
92
+ child.on("error", (err: NodeJS.ErrnoException) =>
93
+ reject(new Error(err.code === "ENOENT" ? "the AWS CLI (aws) is not on PATH; an s3 report URL is uploaded with it" : err.message)),
94
+ );
95
+ child.on("close", (code) => {
96
+ if (code === 0) return resolvePut();
97
+ const line = stderr.trim().split("\n").filter(Boolean).at(-1) ?? "";
98
+ reject(new Error(`aws s3 cp exited ${code}${line ? `: ${line.slice(0, 300)}` : ""}`));
99
+ });
100
+ child.stdin.on("error", () => {
101
+ // The CLI exited before reading its input; its exit says why.
102
+ });
103
+ child.stdin.end(stdin);
104
+ });
105
+
106
+ /** A put for `target`. `aws` replaces the AWS CLI in a test. */
107
+ export function reportPut(target: ReportTarget, aws: AwsCli = realAwsCli): ReportPut {
108
+ if (target.scheme === "file") {
109
+ return async (key, body) => {
110
+ const path = join(target.dir, ...key.split("/"));
111
+ mkdirSync(dirname(path), { recursive: true });
112
+ const temp = `${path}.${process.pid}.tmp`;
113
+ writeFileSync(temp, body);
114
+ renameSync(temp, path);
115
+ };
116
+ }
117
+ return (key, body, contentType) => {
118
+ const object = `s3://${target.bucket}/${target.prefix ? `${target.prefix}/` : ""}${key}`;
119
+ return aws(["s3", "cp", "-", object, "--content-type", contentType, "--only-show-errors"], body);
120
+ };
121
+ }
122
+
123
+ /** A stage's upload: its events as they are written, and its files at the end. */
124
+ export interface PrReportUpload {
125
+ /** The folder the stage's files go to: `<url>/<key>`. */
126
+ readonly where: string;
127
+ /** One line of the stage's run-event stream. A wave, component or run settle uploads the events. */
128
+ line(line: string): void;
129
+ /** A file to upload when the stage closes, replacing one of the same name. */
130
+ file(name: string, body: string): void;
131
+ /** Wait for the upload in flight, then upload the whole stream and the files. Never throws. */
132
+ close(): Promise<void>;
133
+ }
134
+
135
+ const UPLOAD_ON = new Set(["wave-settle", "component-settle", "run-settle"]);
136
+ const NDJSON = "application/x-ndjson";
137
+ const JSON_TYPE = "application/json";
138
+
139
+ const messageOf = (err: unknown): string => (err instanceof Error ? err.message : String(err));
140
+
141
+ /** Open a stage's upload to `target` under `key`. `warn` prints a warning line. */
142
+ export function openPrReportUpload(input: {
143
+ target: ReportTarget;
144
+ key: string;
145
+ stage: "plan" | "apply";
146
+ warn: (message: string) => void;
147
+ put?: ReportPut;
148
+ }): PrReportUpload {
149
+ const put = input.put ?? reportPut(input.target);
150
+ const where = `${input.target.url}/${input.key}`;
151
+ const eventsKey = `${input.key}/${reportEventsFile(input.stage)}`;
152
+ const files = new Map<string, string>();
153
+ let events = "";
154
+ let inFlight: Promise<void> | undefined;
155
+ let again = false;
156
+ let warned = false;
157
+
158
+ const pushEvents = (): void => {
159
+ if (inFlight) {
160
+ again = true;
161
+ return;
162
+ }
163
+ inFlight = put(eventsKey, events, NDJSON)
164
+ .catch((err) => {
165
+ if (warned) return;
166
+ warned = true;
167
+ input.warn(`could not upload the run's events to ${where} as the run went: ${messageOf(err)}. The run goes on, and its end tries again.`);
168
+ })
169
+ .finally(() => {
170
+ inFlight = undefined;
171
+ if (again) {
172
+ again = false;
173
+ pushEvents();
174
+ }
175
+ });
176
+ };
177
+
178
+ return {
179
+ where,
180
+ line(line) {
181
+ events += line;
182
+ let type: unknown;
183
+ try {
184
+ type = (JSON.parse(line) as { type?: unknown }).type;
185
+ } catch {
186
+ return;
187
+ }
188
+ if (typeof type === "string" && UPLOAD_ON.has(type)) pushEvents();
189
+ },
190
+ file(name, body) {
191
+ files.set(name, body);
192
+ },
193
+ async close() {
194
+ again = false;
195
+ while (inFlight) await inFlight;
196
+ const uploads: Array<[string, string, string]> = [
197
+ ...(events ? [[eventsKey, events, NDJSON] as [string, string, string]] : []),
198
+ ...[...files].map(([name, body]): [string, string, string] => [`${input.key}/${name}`, body, JSON_TYPE]),
199
+ ];
200
+ for (const [key, body, type] of uploads) {
201
+ try {
202
+ await put(key, body, type);
203
+ } catch (err) {
204
+ input.warn(`could not upload ${key.slice(input.key.length + 1)} to ${where}: ${messageOf(err)}`);
205
+ }
206
+ }
207
+ },
208
+ };
209
+ }
package/src/provenance.ts CHANGED
@@ -297,6 +297,6 @@ export interface BuildParamProvenance {
297
297
  name: string;
298
298
  /** The resolved, type-coerced value actually bound to `params.<name>` for this build. */
299
299
  value: string | number | boolean;
300
- /** Which input supplied the value. */
301
- source: "cli" | "params-file" | "env" | "default";
300
+ /** Which input supplied the value: `flag:<name>` names the workspace flag record it came from (#4024). */
301
+ source: "cli" | "params-file" | "env" | "default" | `flag:${string}`;
302
302
  }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * #4042: a run's events kept on this host, replayed, and followed while the
3
+ * run is in progress. No servers: a temp git checkout, the real executor and
4
+ * the real writer, and a short poll.
5
+ */
6
+
7
+ import { execFileSync } from "node:child_process";
8
+ import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync, appendFileSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
12
+ import { runOpLocally } from "./op/local-executor";
13
+ import { memoryGateLedgerPort } from "./op/gate";
14
+ import type { ActivityFn } from "./op/activity-registry";
15
+ import type { OpConfig } from "./op/types";
16
+ import type { RunEvent } from "./run-event";
17
+ import { openRunEventStream, runEventRunOf } from "./run-event-stream";
18
+ import { findStoredRun, listStoredRuns, openRunEventStore, readRunEvents, runEventsDir } from "./run-event-store";
19
+ import { checkRunEventLines } from "./workspace/__fixtures__/run-event-check";
20
+
21
+ const dirs: string[] = [];
22
+ // These tests read the store a temp checkout keeps under its own git directory.
23
+ beforeEach(() => void vi.stubEnv("CHANT_RUN_EVENTS_DIR", ""));
24
+ afterEach(() => {
25
+ vi.unstubAllEnvs();
26
+ for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
27
+ });
28
+
29
+ function checkout(): string {
30
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "run-event-store-")));
31
+ dirs.push(dir);
32
+ execFileSync("git", ["init", "-q", "-b", "main"], { cwd: dir });
33
+ execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "commit", "-q", "--allow-empty", "-m", "init"], { cwd: dir });
34
+ return dir;
35
+ }
36
+
37
+ const op: OpConfig = {
38
+ name: "deploy-api",
39
+ overview: "",
40
+ phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }] }],
41
+ };
42
+ const ok: ActivityFn = async () => ({ done: true });
43
+ const PROFILES = { fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } } };
44
+
45
+ describe("a settled run's events", () => {
46
+ test("a run that records to the ledger keeps its stream, and it replays as the writer wrote it", async () => {
47
+ const dir = checkout();
48
+ await runOpLocally(op, new Map([["make", ok]]), PROFILES, undefined, {
49
+ runId: "r-settled",
50
+ ledger: { cwd: dir },
51
+ cwd: dir,
52
+ gates: memoryGateLedgerPort(),
53
+ });
54
+ const stored = (await findStoredRun(dir, "r-settled"))!;
55
+ expect(stored.file.startsWith(join(dir, ".git", "chant", "run-events"))).toBe(true);
56
+ expect(stored.name).toBe("deploy-api");
57
+
58
+ const lines: string[] = [];
59
+ const settle = await readRunEvents(stored.file, (_e, line) => lines.push(line));
60
+ const events = checkRunEventLines(lines);
61
+ expect(settle?.type).toBe("run-settle");
62
+ expect(events.map((e) => e.type)).toContain("step-settle");
63
+ expect(lines.join("")).toBe(readFileSync(stored.file, "utf8"));
64
+ });
65
+
66
+ test("an Op name stands for its newest run", async () => {
67
+ const dir = checkout();
68
+ for (const runId of ["r-1", "r-2"]) {
69
+ await runOpLocally(op, new Map([["make", ok]]), PROFILES, undefined, { runId, ledger: { cwd: dir }, cwd: dir, gates: memoryGateLedgerPort() });
70
+ await new Promise((r) => setTimeout(r, 15));
71
+ }
72
+ expect((await findStoredRun(dir, "deploy-api"))?.runId).toBe("r-2");
73
+ expect((await listStoredRuns(dir)).map((r) => r.runId)).toEqual(["r-2", "r-1"]);
74
+ expect(await findStoredRun(dir, "nothing")).toBeNull();
75
+ });
76
+
77
+ test("a run with no ledger (--no-record) keeps nothing", async () => {
78
+ const dir = checkout();
79
+ await runOpLocally(op, new Map([["make", ok]]), PROFILES, undefined, { runId: "r-none", cwd: dir, gates: memoryGateLedgerPort() });
80
+ const events = await runEventsDir(dir);
81
+ expect(events === null || !existsSync(events)).toBe(true);
82
+ });
83
+ });
84
+
85
+ describe("a run in progress", () => {
86
+ async function inFlight() {
87
+ const dir = checkout();
88
+ const store = (await openRunEventStore(dir, "r-live"))!;
89
+ const stream = openRunEventStream(runEventRunOf({ kind: "op", name: "deploy-api", runId: "r-live" }), store.write);
90
+ return { dir, store, stream };
91
+ }
92
+
93
+ test("replays what has been written so far, and not a half-written last line", async () => {
94
+ const { store, stream } = await inFlight();
95
+ stream.emit({ type: "run-start", waves: null });
96
+ stream.emit({ type: "phase-start", component: null, phase: "Build" });
97
+ appendFileSync(store.path, '{"type":"step-st');
98
+ const seen: RunEvent[] = [];
99
+ const settle = await readRunEvents(store.path, (e) => seen.push(e));
100
+ expect(settle).toBeUndefined();
101
+ expect(seen.map((e) => e.type)).toEqual(["run-start", "phase-start"]);
102
+ });
103
+
104
+ test("--follow streams each event as it is written and ends at run-settle", async () => {
105
+ const { store, stream } = await inFlight();
106
+ stream.emit({ type: "run-start", waves: null });
107
+ const seen: string[] = [];
108
+ const follow = readRunEvents(store.path, (e) => seen.push(`${e.seq}:${e.type}`), { follow: true, pollMs: 5, idleMs: 3000 });
109
+
110
+ await new Promise((r) => setTimeout(r, 30));
111
+ expect(seen).toEqual(["0:run-start"]);
112
+ stream.emit({ type: "phase-start", component: null, phase: "Build" });
113
+ stream.emit({ type: "step-start", component: null, phase: "Build", step: "make" });
114
+ await new Promise((r) => setTimeout(r, 30));
115
+ expect(seen).toEqual(["0:run-start", "1:phase-start", "2:step-start"]);
116
+ stream.settle("ok");
117
+
118
+ const settle = await follow;
119
+ expect(settle?.type).toBe("run-settle");
120
+ expect(seen.at(-1)).toBe("3:run-settle");
121
+ });
122
+
123
+ test("--follow waits for a file that is not there yet", async () => {
124
+ const dir = checkout();
125
+ const file = join((await runEventsDir(dir))!, "later.jsonl");
126
+ const seen: string[] = [];
127
+ const follow = readRunEvents(file, (e) => seen.push(e.type), { follow: true, pollMs: 5, idleMs: 3000 });
128
+ await new Promise((r) => setTimeout(r, 20));
129
+ const store = (await openRunEventStore(dir, "later"))!;
130
+ const stream = openRunEventStream(runEventRunOf({ kind: "op", name: "x", runId: "later" }), store.write);
131
+ stream.settle("failed", "boom");
132
+ expect((await follow)?.type).toBe("run-settle");
133
+ expect(seen).toEqual(["run-start", "run-settle"]);
134
+ });
135
+
136
+ test("an aborted follow returns without a verdict", async () => {
137
+ const { store, stream } = await inFlight();
138
+ stream.emit({ type: "run-start", waves: null });
139
+ const ac = new AbortController();
140
+ const follow = readRunEvents(store.path, () => {}, { follow: true, pollMs: 5, signal: ac.signal });
141
+ setTimeout(() => ac.abort(), 20);
142
+ expect(await follow).toBeUndefined();
143
+ });
144
+ });
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Where a run's events are kept on this host, and how to read them back
3
+ * (#4042). `chant run events <run>` replays a run from here and `--follow`
4
+ * tails it.
5
+ *
6
+ * Each run gets one file, `<git-common-dir>/chant/run-events/<run id>.jsonl`,
7
+ * holding the lines the one writer (`./run-event.ts`) produced, byte for byte
8
+ * as `--events-json` prints them. It sits beside the in-flight records
9
+ * (`./op/run-live.ts`), outside the working tree and off `chant/lifecycle`:
10
+ * the run ledger's growth is #4036's concern and these files add none to it.
11
+ * Unlike the in-flight record they are kept after the run settles, until
12
+ * `chant lifecycle gc --yes` deletes the ones its retention policy lets go
13
+ * (#4036, {@link planRunEventPrune}).
14
+ *
15
+ * Every write is best-effort. A run whose events cannot be kept still runs.
16
+ */
17
+
18
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { gitCommonDir } from "./op/run-live";
21
+ import type { RunEvent } from "./run-event";
22
+
23
+ /** The directory name under `<git-common-dir>/chant/` that holds the files. */
24
+ export const RUN_EVENTS_DIR = "run-events";
25
+
26
+ /** The file name a run id is kept under: anything that is not a plain file-name character becomes `_`. */
27
+ export function runEventFileName(runId: string): string {
28
+ return `${runId.replace(/[^A-Za-z0-9._@-]/g, "_")}.jsonl`;
29
+ }
30
+
31
+ /**
32
+ * The environment variable that names the directory run events are kept in,
33
+ * in place of the checkout's. The test suite sets it to a per-run temp
34
+ * directory, so no test writes to the real repository's store (worktrees
35
+ * share one git directory).
36
+ */
37
+ export const RUN_EVENTS_DIR_ENV = "CHANT_RUN_EVENTS_DIR";
38
+
39
+ /** The directory the checkout at `cwd` keeps run events in (`CHANT_RUN_EVENTS_DIR` when set), or null outside a checkout. */
40
+ export async function runEventsDir(cwd: string): Promise<string | null> {
41
+ const override = process.env[RUN_EVENTS_DIR_ENV];
42
+ if (override) return override;
43
+ const common = await gitCommonDir(cwd);
44
+ return common ? join(common, "chant", RUN_EVENTS_DIR) : null;
45
+ }
46
+
47
+ /** An open file a run's lines are appended to. */
48
+ export interface RunEventStore {
49
+ readonly path: string;
50
+ /** Append one line (it already ends with a newline). Never throws. */
51
+ write(line: string): void;
52
+ }
53
+
54
+ /** Open the file for `runId`, or undefined outside a checkout or where it cannot be created. */
55
+ export async function openRunEventStore(cwd: string, runId: string): Promise<RunEventStore | undefined> {
56
+ let dir: string | null;
57
+ try {
58
+ dir = await runEventsDir(cwd);
59
+ } catch {
60
+ return undefined;
61
+ }
62
+ if (!dir) return undefined;
63
+ const path = join(dir, runEventFileName(runId));
64
+ try {
65
+ mkdirSync(dir, { recursive: true });
66
+ // A run id used again (a re-run of the same CI run) replaces the earlier stream.
67
+ writeFileSync(path, "");
68
+ } catch {
69
+ return undefined;
70
+ }
71
+ return {
72
+ path,
73
+ write(line) {
74
+ try {
75
+ appendFileSync(path, line);
76
+ } catch {
77
+ // The directory is gone or unwritable: the line is dropped.
78
+ }
79
+ },
80
+ };
81
+ }
82
+
83
+ /** A write function that hands each line to every function given, or undefined when none is. */
84
+ export function teeLines(...writes: Array<((line: string) => void) | undefined>): ((line: string) => void) | undefined {
85
+ const live = writes.filter((w): w is (line: string) => void => w !== undefined);
86
+ if (live.length === 0) return undefined;
87
+ if (live.length === 1) return live[0];
88
+ return (line) => {
89
+ for (const w of live) w(line);
90
+ };
91
+ }
92
+
93
+ /** The events in `text`, one per whole line. Returns what is left of an unfinished last line. */
94
+ export function parseRunEventLines(text: string): { events: RunEvent[]; rest: string } {
95
+ const events: RunEvent[] = [];
96
+ const parts = text.split("\n");
97
+ const rest = parts.pop() ?? "";
98
+ for (const part of parts) {
99
+ if (part.trim() === "") continue;
100
+ try {
101
+ events.push(JSON.parse(part) as RunEvent);
102
+ } catch {
103
+ // A line that is not JSON is not an event: skipped.
104
+ }
105
+ }
106
+ return { events, rest };
107
+ }
108
+
109
+ /** A stored run, as far as its first line says. */
110
+ export interface StoredRun {
111
+ runId: string;
112
+ name: string;
113
+ file: string;
114
+ /** The file's last write, in ms. */
115
+ mtimeMs: number;
116
+ }
117
+
118
+ /** The runs stored for the checkout at `cwd`, newest first. */
119
+ export async function listStoredRuns(cwd: string): Promise<StoredRun[]> {
120
+ const dir = await runEventsDir(cwd);
121
+ if (!dir || !existsSync(dir)) return [];
122
+ const runs: StoredRun[] = [];
123
+ for (const entry of readdirSync(dir)) {
124
+ if (!entry.endsWith(".jsonl")) continue;
125
+ const file = join(dir, entry);
126
+ try {
127
+ const first = readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
128
+ if (first === "") continue;
129
+ const head = JSON.parse(first) as Partial<RunEvent>;
130
+ if (typeof head.runId !== "string") continue;
131
+ runs.push({ runId: head.runId, name: String(head.name ?? ""), file, mtimeMs: statSync(file).mtimeMs });
132
+ } catch {
133
+ // Unreadable or not an event file: not listed.
134
+ }
135
+ }
136
+ return runs.sort((a, b) => b.mtimeMs - a.mtimeMs);
137
+ }
138
+
139
+ /**
140
+ * The file for `ref`: a run id, else the name of an Op or a component
141
+ * selection, for which the newest stored run of that name stands. Null when
142
+ * nothing matches (a run that has not written its first line yet included).
143
+ */
144
+ export async function findStoredRun(cwd: string, ref: string): Promise<StoredRun | null> {
145
+ const dir = await runEventsDir(cwd);
146
+ if (!dir) return null;
147
+ const direct = join(dir, runEventFileName(ref));
148
+ if (existsSync(direct)) {
149
+ const byId = (await listStoredRuns(cwd)).find((r) => r.file === direct);
150
+ return byId ?? { runId: ref, name: "", file: direct, mtimeMs: statSync(direct).mtimeMs };
151
+ }
152
+ return (await listStoredRuns(cwd)).find((r) => r.name === ref) ?? null;
153
+ }
154
+
155
+ /** Whether `event` ends a stream. */
156
+ export const isRunSettle = (event: RunEvent): boolean => event.type === "run-settle";
157
+
158
+ /**
159
+ * Read `file` from the start and hand each event to `onEvent` (with its raw
160
+ * line). Without `follow` it stops at the end of the file. With it, it keeps
161
+ * reading what is appended, by polling every `pollMs`, until `run-settle` is
162
+ * read, `signal` aborts, or `idleMs` passes with nothing new (when given).
163
+ * Resolves with the `run-settle` event, or undefined when the stream had
164
+ * none (a replay of a run still going, or a follow that was cut short).
165
+ */
166
+ export async function readRunEvents(
167
+ file: string,
168
+ onEvent: (event: RunEvent, line: string) => void,
169
+ opts: { follow?: boolean; pollMs?: number; idleMs?: number; signal?: AbortSignal } = {},
170
+ ): Promise<RunEvent | undefined> {
171
+ const pollMs = opts.pollMs ?? 100;
172
+ let offset = 0;
173
+ let rest = "";
174
+ let lastGrowth = Date.now();
175
+ for (;;) {
176
+ let size = 0;
177
+ try {
178
+ size = statSync(file).size;
179
+ } catch {
180
+ // Not there yet (a follow started before the run opened its file).
181
+ }
182
+ if (size > offset) {
183
+ const buf = readFileSync(file);
184
+ const chunk = buf.subarray(offset, size).toString("utf8");
185
+ offset = size;
186
+ lastGrowth = Date.now();
187
+ const text = rest + chunk;
188
+ const parts = text.split("\n");
189
+ rest = parts.pop() ?? "";
190
+ for (const part of parts) {
191
+ if (part.trim() === "") continue;
192
+ let event: RunEvent;
193
+ try {
194
+ event = JSON.parse(part) as RunEvent;
195
+ } catch {
196
+ continue;
197
+ }
198
+ onEvent(event, `${part}\n`);
199
+ if (isRunSettle(event)) return event;
200
+ }
201
+ } else if (!opts.follow) {
202
+ return undefined;
203
+ } else if (opts.signal?.aborted) {
204
+ return undefined;
205
+ } else if (opts.idleMs !== undefined && Date.now() - lastGrowth > opts.idleMs) {
206
+ return undefined;
207
+ } else {
208
+ await new Promise<void>((resolve) => {
209
+ const timer = setTimeout(resolve, pollMs);
210
+ opts.signal?.addEventListener("abort", () => { clearTimeout(timer); resolve(); }, { once: true });
211
+ });
212
+ }
213
+ }
214
+ }
215
+
216
+ /** One run's event file, as retention sees it (#4036). */
217
+ export interface RunEventFile {
218
+ file: string;
219
+ bytes: number;
220
+ /** The file's last write, ISO-8601. */
221
+ modified: string;
222
+ }
223
+
224
+ /** What {@link planRunEventPrune} would delete and keep. */
225
+ export interface RunEventPrune {
226
+ /** The directory read, or null outside a checkout. */
227
+ dir: string | null;
228
+ remove: RunEventFile[];
229
+ keep: RunEventFile[];
230
+ }
231
+
232
+ /**
233
+ * Which run event files the policy lets go (#4036): every file older than
234
+ * `days` by its last write, apart from the newest `keep`. A run still going
235
+ * writes its file as it goes, so it is never older than the window. Reads
236
+ * only; {@link pruneRunEvents} deletes.
237
+ */
238
+ export async function planRunEventPrune(cwd: string, policy: { days: number; keep: number }, now: Date): Promise<RunEventPrune> {
239
+ const dir = await runEventsDir(cwd);
240
+ if (!dir || !existsSync(dir)) return { dir, remove: [], keep: [] };
241
+ const files: Array<RunEventFile & { ms: number }> = [];
242
+ for (const entry of readdirSync(dir)) {
243
+ if (!entry.endsWith(".jsonl")) continue;
244
+ const file = join(dir, entry);
245
+ try {
246
+ const st = statSync(file);
247
+ if (!st.isFile()) continue;
248
+ files.push({ file, bytes: st.size, modified: new Date(st.mtimeMs).toISOString(), ms: st.mtimeMs });
249
+ } catch {
250
+ // Gone between the listing and the stat.
251
+ }
252
+ }
253
+ files.sort((a, b) => b.ms - a.ms || (a.file < b.file ? -1 : 1));
254
+ const cutoff = now.getTime() - policy.days * 86_400_000;
255
+ const remove: RunEventFile[] = [];
256
+ const keep: RunEventFile[] = [];
257
+ files.forEach(({ ms, ...f }, i) => (i >= policy.keep && ms < cutoff ? remove : keep).push(f));
258
+ return { dir, remove, keep };
259
+ }
260
+
261
+ /** Delete the files a plan lets go. Returns how many were deleted; a file already gone is not counted. */
262
+ export function pruneRunEvents(plan: RunEventPrune): number {
263
+ let n = 0;
264
+ for (const f of plan.remove) {
265
+ try {
266
+ rmSync(f.file);
267
+ n++;
268
+ } catch {
269
+ // Already gone.
270
+ }
271
+ }
272
+ return n;
273
+ }