@intentius/chant 0.124.26 → 0.124.28

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 (459) 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/change-set-graph.d.ts +56 -0
  20. package/dist/change-set-graph.d.ts.map +1 -0
  21. package/dist/change-set-graph.js +128 -0
  22. package/dist/change-set.d.ts +52 -3
  23. package/dist/change-set.d.ts.map +1 -1
  24. package/dist/change-set.js +35 -4
  25. package/dist/chant-version.d.ts +9 -0
  26. package/dist/chant-version.d.ts.map +1 -0
  27. package/dist/chant-version.js +16 -0
  28. package/dist/cli/commands/build.d.ts +8 -0
  29. package/dist/cli/commands/build.d.ts.map +1 -1
  30. package/dist/cli/commands/build.js +9 -8
  31. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  32. package/dist/cli/commands/check-lexicon-docs.js +30 -2
  33. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  34. package/dist/cli/commands/check-lexicon.js +13 -2
  35. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
  36. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  37. package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
  38. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  39. package/dist/cli/commands/init-lexicon.js +17 -8
  40. package/dist/cli/handlers/approve.d.ts.map +1 -1
  41. package/dist/cli/handlers/approve.js +19 -4
  42. package/dist/cli/handlers/build.d.ts.map +1 -1
  43. package/dist/cli/handlers/build.js +28 -0
  44. package/dist/cli/handlers/change-set.d.ts +10 -0
  45. package/dist/cli/handlers/change-set.d.ts.map +1 -1
  46. package/dist/cli/handlers/change-set.js +51 -1
  47. package/dist/cli/handlers/fan-out.d.ts +1 -1
  48. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  49. package/dist/cli/handlers/fan-out.js +14 -3
  50. package/dist/cli/handlers/pr.d.ts +8 -2
  51. package/dist/cli/handlers/pr.d.ts.map +1 -1
  52. package/dist/cli/handlers/pr.js +148 -24
  53. package/dist/cli/handlers/promote.d.ts.map +1 -1
  54. package/dist/cli/handlers/promote.js +256 -21
  55. package/dist/cli/handlers/release.d.ts +34 -0
  56. package/dist/cli/handlers/release.d.ts.map +1 -0
  57. package/dist/cli/handlers/release.js +203 -0
  58. package/dist/cli/handlers/run.d.ts +28 -2
  59. package/dist/cli/handlers/run.d.ts.map +1 -1
  60. package/dist/cli/handlers/run.js +170 -13
  61. package/dist/cli/main.d.ts.map +1 -1
  62. package/dist/cli/main.js +88 -13
  63. package/dist/cli/mcp/workspace-tools.js +1 -1
  64. package/dist/cli/parse-args.d.ts.map +1 -1
  65. package/dist/cli/parse-args.js +49 -3
  66. package/dist/cli/registry.d.ts +29 -3
  67. package/dist/cli/registry.d.ts.map +1 -1
  68. package/dist/cli/run-events.d.ts +29 -0
  69. package/dist/cli/run-events.d.ts.map +1 -0
  70. package/dist/cli/run-events.js +47 -0
  71. package/dist/cli/version.d.ts +3 -5
  72. package/dist/cli/version.d.ts.map +1 -1
  73. package/dist/cli/version.js +3 -13
  74. package/dist/codegen/docs-in-site.d.ts +71 -0
  75. package/dist/codegen/docs-in-site.d.ts.map +1 -0
  76. package/dist/codegen/docs-in-site.js +177 -0
  77. package/dist/codegen/docs-types.d.ts +14 -1
  78. package/dist/codegen/docs-types.d.ts.map +1 -1
  79. package/dist/codegen/docs.d.ts +18 -1
  80. package/dist/codegen/docs.d.ts.map +1 -1
  81. package/dist/codegen/docs.js +40 -7
  82. package/dist/codegen/rehype-base-url.mjs +15 -9
  83. package/dist/components/auto-release.d.ts +4 -1
  84. package/dist/components/auto-release.d.ts.map +1 -1
  85. package/dist/components/auto-release.js +2 -0
  86. package/dist/components/cli-support.d.ts +6 -0
  87. package/dist/components/cli-support.d.ts.map +1 -1
  88. package/dist/components/cli-support.js +10 -9
  89. package/dist/components/driver.d.ts +42 -9
  90. package/dist/components/driver.d.ts.map +1 -1
  91. package/dist/components/driver.js +81 -54
  92. package/dist/components/fan-out-run.d.ts +7 -0
  93. package/dist/components/fan-out-run.d.ts.map +1 -1
  94. package/dist/components/fan-out-run.js +35 -26
  95. package/dist/components/pr-run.d.ts +11 -0
  96. package/dist/components/pr-run.d.ts.map +1 -1
  97. package/dist/components/pr-run.js +20 -1
  98. package/dist/components/promote-order.d.ts +38 -0
  99. package/dist/components/promote-order.d.ts.map +1 -0
  100. package/dist/components/promote-order.js +62 -0
  101. package/dist/components/promote.d.ts +15 -2
  102. package/dist/components/promote.d.ts.map +1 -1
  103. package/dist/components/promote.js +17 -0
  104. package/dist/components/release-compile.d.ts +133 -0
  105. package/dist/components/release-compile.d.ts.map +1 -0
  106. package/dist/components/release-compile.js +376 -0
  107. package/dist/components/release-gate.d.ts +125 -0
  108. package/dist/components/release-gate.d.ts.map +1 -0
  109. package/dist/components/release-gate.js +282 -0
  110. package/dist/components/release-promote.d.ts +96 -0
  111. package/dist/components/release-promote.d.ts.map +1 -0
  112. package/dist/components/release-promote.js +204 -0
  113. package/dist/components/run-progress.d.ts +7 -0
  114. package/dist/components/run-progress.d.ts.map +1 -1
  115. package/dist/components/wave-plan.d.ts +3 -1
  116. package/dist/components/wave-plan.d.ts.map +1 -1
  117. package/dist/components/wave-plan.js +1 -1
  118. package/dist/config.d.ts +7 -0
  119. package/dist/config.d.ts.map +1 -1
  120. package/dist/config.js +1 -0
  121. package/dist/index.d.ts +1 -0
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +2 -0
  124. package/dist/lifecycle/change-set-store.d.ts +67 -0
  125. package/dist/lifecycle/change-set-store.d.ts.map +1 -0
  126. package/dist/lifecycle/change-set-store.js +96 -0
  127. package/dist/lifecycle/commit-sha.d.ts +13 -0
  128. package/dist/lifecycle/commit-sha.d.ts.map +1 -0
  129. package/dist/lifecycle/commit-sha.js +21 -0
  130. package/dist/lifecycle/gate-change-set.d.ts +69 -0
  131. package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
  132. package/dist/lifecycle/gate-change-set.js +106 -0
  133. package/dist/lifecycle/gate-ledger.d.ts +49 -1
  134. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  135. package/dist/lifecycle/gate-ledger.js +18 -0
  136. package/dist/lifecycle/release-cut.d.ts +107 -0
  137. package/dist/lifecycle/release-cut.d.ts.map +1 -0
  138. package/dist/lifecycle/release-cut.js +273 -0
  139. package/dist/lifecycle/release-ledger.d.ts +54 -0
  140. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  141. package/dist/lifecycle/release-ledger.js +53 -20
  142. package/dist/lifecycle/release-store.d.ts +190 -0
  143. package/dist/lifecycle/release-store.d.ts.map +1 -0
  144. package/dist/lifecycle/release-store.js +318 -0
  145. package/dist/lifecycle/run-ledger.d.ts +4 -0
  146. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  147. package/dist/lifecycle/run-ledger.js +2 -0
  148. package/dist/op/activities/change-coverage.d.ts.map +1 -1
  149. package/dist/op/activities/change-coverage.js +4 -2
  150. package/dist/op/activities/source-release.d.ts.map +1 -1
  151. package/dist/op/activities/source-release.js +7 -3
  152. package/dist/op/activities/source-rollback.d.ts.map +1 -1
  153. package/dist/op/activities/source-rollback.js +7 -3
  154. package/dist/op/activity-registry.d.ts +6 -3
  155. package/dist/op/activity-registry.d.ts.map +1 -1
  156. package/dist/op/activity-registry.js +7 -5
  157. package/dist/op/discover.d.ts.map +1 -1
  158. package/dist/op/discover.js +14 -7
  159. package/dist/op/gate-name.d.ts +11 -2
  160. package/dist/op/gate-name.d.ts.map +1 -1
  161. package/dist/op/gate-name.js +12 -3
  162. package/dist/op/gate-run-locator.d.ts +22 -0
  163. package/dist/op/gate-run-locator.d.ts.map +1 -1
  164. package/dist/op/gate-run-locator.js +31 -0
  165. package/dist/op/gate.d.ts +27 -3
  166. package/dist/op/gate.d.ts.map +1 -1
  167. package/dist/op/gate.js +57 -3
  168. package/dist/op/local-executor.d.ts +36 -1
  169. package/dist/op/local-executor.d.ts.map +1 -1
  170. package/dist/op/local-executor.js +158 -52
  171. package/dist/op/op-waves-run.d.ts.map +1 -1
  172. package/dist/op/op-waves-run.js +4 -0
  173. package/dist/op/run-live.d.ts +2 -0
  174. package/dist/op/run-live.d.ts.map +1 -1
  175. package/dist/op/run-live.js +1 -1
  176. package/dist/op/runtime.d.ts +46 -0
  177. package/dist/op/runtime.d.ts.map +1 -1
  178. package/dist/op/runtimes/local.d.ts.map +1 -1
  179. package/dist/op/runtimes/local.js +12 -2
  180. package/dist/point-decision.d.ts +2 -0
  181. package/dist/point-decision.d.ts.map +1 -1
  182. package/dist/point-decision.js +9 -2
  183. package/dist/run-event-store.d.ts +77 -0
  184. package/dist/run-event-store.d.ts.map +1 -0
  185. package/dist/run-event-store.js +204 -0
  186. package/dist/run-event-stream.d.ts +132 -0
  187. package/dist/run-event-stream.d.ts.map +1 -0
  188. package/dist/run-event-stream.js +238 -0
  189. package/dist/run-event.d.ts +267 -0
  190. package/dist/run-event.d.ts.map +1 -0
  191. package/dist/run-event.js +207 -0
  192. package/dist/wave-runtime.mjs +225 -242
  193. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  194. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  195. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  196. package/dist/workspace/change-set-show.d.ts +56 -0
  197. package/dist/workspace/change-set-show.d.ts.map +1 -0
  198. package/dist/workspace/change-set-show.js +54 -0
  199. package/dist/workspace/change-set-show.schema.json +102 -0
  200. package/dist/workspace/change-set.schema.json +28 -1
  201. package/dist/workspace/changes-cli.d.ts.map +1 -1
  202. package/dist/workspace/changes-cli.js +10 -1
  203. package/dist/workspace/changes.d.ts +37 -0
  204. package/dist/workspace/changes.d.ts.map +1 -1
  205. package/dist/workspace/changes.js +49 -2
  206. package/dist/workspace/changes.schema.json +135 -0
  207. package/dist/workspace/ci-green-cli.d.ts +19 -4
  208. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  209. package/dist/workspace/ci-green-cli.js +75 -12
  210. package/dist/workspace/ci-green-forge.d.ts +20 -3
  211. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  212. package/dist/workspace/ci-green-forge.js +6 -4
  213. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  214. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  215. package/dist/workspace/ci-green-workflow.js +6 -0
  216. package/dist/workspace/ci-green.d.ts +273 -13
  217. package/dist/workspace/ci-green.d.ts.map +1 -1
  218. package/dist/workspace/ci-green.js +449 -38
  219. package/dist/workspace/ci-last-green.schema.json +32 -2
  220. package/dist/workspace/ci-list.schema.json +525 -0
  221. package/dist/workspace/commit-links.d.ts +85 -0
  222. package/dist/workspace/commit-links.d.ts.map +1 -0
  223. package/dist/workspace/commit-links.js +163 -0
  224. package/dist/workspace/declaration.d.ts +19 -0
  225. package/dist/workspace/declaration.d.ts.map +1 -1
  226. package/dist/workspace/declaration.js +1 -1
  227. package/dist/workspace/declaration.schema.json +12 -0
  228. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  229. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  230. package/dist/workspace/identity-gate-rule.js +72 -7
  231. package/dist/workspace/identity.d.ts +12 -0
  232. package/dist/workspace/identity.d.ts.map +1 -1
  233. package/dist/workspace/identity.js +13 -1
  234. package/dist/workspace/intent.d.ts +6 -1
  235. package/dist/workspace/intent.d.ts.map +1 -1
  236. package/dist/workspace/intent.js +51 -1
  237. package/dist/workspace/ls.d.ts +4 -0
  238. package/dist/workspace/ls.d.ts.map +1 -1
  239. package/dist/workspace/ls.js +2 -0
  240. package/dist/workspace/ls.schema.json +3 -1
  241. package/dist/workspace/member-pipeline.d.ts +6 -0
  242. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  243. package/dist/workspace/member-pipeline.js +6 -0
  244. package/dist/workspace/promotion-order.d.ts +41 -0
  245. package/dist/workspace/promotion-order.d.ts.map +1 -0
  246. package/dist/workspace/promotion-order.js +95 -0
  247. package/dist/workspace/reason-codes.d.ts +2 -0
  248. package/dist/workspace/reason-codes.d.ts.map +1 -1
  249. package/dist/workspace/reason-codes.js +4 -1
  250. package/dist/workspace/releases.d.ts +72 -0
  251. package/dist/workspace/releases.d.ts.map +1 -0
  252. package/dist/workspace/releases.js +94 -0
  253. package/dist/workspace/releases.schema.json +391 -0
  254. package/dist/workspace/run-event.schema.json +288 -0
  255. package/dist/workspace/status-gates.d.ts +26 -1
  256. package/dist/workspace/status-gates.d.ts.map +1 -1
  257. package/dist/workspace/status-gates.js +31 -2
  258. package/dist/workspace/status-stewards.d.ts +4 -0
  259. package/dist/workspace/status-stewards.d.ts.map +1 -1
  260. package/dist/workspace/status-stewards.js +2 -0
  261. package/dist/workspace/status.d.ts +39 -0
  262. package/dist/workspace/status.d.ts.map +1 -1
  263. package/dist/workspace/status.js +48 -1
  264. package/dist/workspace/status.schema.json +70 -1
  265. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  266. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  267. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  268. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  269. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  270. package/dist/workspace/trust/op-run-statement.js +143 -0
  271. package/dist/workspace/trust/rotation.d.ts +18 -2
  272. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  273. package/dist/workspace/trust/rotation.js +65 -16
  274. package/dist/workspace/trust/seal.d.ts +11 -0
  275. package/dist/workspace/trust/seal.d.ts.map +1 -1
  276. package/dist/workspace/trust/seal.js +9 -0
  277. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  278. package/dist/workspace/trust/ssh-commit.js +10 -10
  279. package/package.json +1 -1
  280. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  281. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  282. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  283. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  284. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  285. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  286. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  287. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  288. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  289. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  290. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  291. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  292. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  293. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  294. package/src/__golden__/wave-loop/op-spend.json +3 -0
  295. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  296. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  297. package/src/change-set-graph.test.ts +72 -0
  298. package/src/change-set-graph.ts +133 -0
  299. package/src/change-set.ts +76 -6
  300. package/src/chant-version.ts +16 -0
  301. package/src/cli/commands/build.ts +19 -8
  302. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  303. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  304. package/src/cli/commands/check-lexicon.ts +14 -2
  305. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  306. package/src/cli/commands/init-lexicon.test.ts +25 -0
  307. package/src/cli/commands/init-lexicon.ts +18 -8
  308. package/src/cli/compiled-cli.e2e.test.ts +31 -6
  309. package/src/cli/handlers/approve-release.test.ts +80 -0
  310. package/src/cli/handlers/approve.ts +18 -2
  311. package/src/cli/handlers/build.test.ts +52 -0
  312. package/src/cli/handlers/build.ts +29 -0
  313. package/src/cli/handlers/change-set.ts +49 -1
  314. package/src/cli/handlers/fan-out.test.ts +48 -0
  315. package/src/cli/handlers/fan-out.ts +15 -3
  316. package/src/cli/handlers/operator.test.ts +37 -0
  317. package/src/cli/handlers/pr.test.ts +194 -0
  318. package/src/cli/handlers/pr.ts +168 -34
  319. package/src/cli/handlers/promote.test.ts +389 -5
  320. package/src/cli/handlers/promote.ts +279 -20
  321. package/src/cli/handlers/release-implicit.e2e.test.ts +153 -0
  322. package/src/cli/handlers/release.e2e.test.ts +85 -0
  323. package/src/cli/handlers/release.ts +206 -0
  324. package/src/cli/handlers/run-auto-release.test.ts +71 -0
  325. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  326. package/src/cli/handlers/run.test.ts +30 -0
  327. package/src/cli/handlers/run.ts +176 -15
  328. package/src/cli/main.test.ts +7 -0
  329. package/src/cli/main.ts +88 -13
  330. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  331. package/src/cli/mcp/workspace-tools.ts +1 -1
  332. package/src/cli/parse-args.ts +37 -3
  333. package/src/cli/registry.ts +29 -3
  334. package/src/cli/run-events.ts +54 -0
  335. package/src/cli/version.ts +3 -13
  336. package/src/codegen/docs-in-site.test.ts +166 -0
  337. package/src/codegen/docs-in-site.ts +182 -0
  338. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  339. package/src/codegen/docs-types.ts +14 -1
  340. package/src/codegen/docs.ts +60 -7
  341. package/src/codegen/rehype-base-url.d.mts +3 -1
  342. package/src/codegen/rehype-base-url.mjs +15 -9
  343. package/src/codegen/rehype-base-url.test.ts +34 -0
  344. package/src/components/auto-release.test.ts +21 -0
  345. package/src/components/auto-release.ts +6 -1
  346. package/src/components/cli-support.test.ts +82 -0
  347. package/src/components/cli-support.ts +16 -9
  348. package/src/components/driver.ts +129 -56
  349. package/src/components/fan-out-run.ts +56 -35
  350. package/src/components/pr-run.ts +30 -1
  351. package/src/components/promote-order.test.ts +90 -0
  352. package/src/components/promote-order.ts +75 -0
  353. package/src/components/promote.test.ts +10 -1
  354. package/src/components/promote.ts +26 -1
  355. package/src/components/release-compile.test.ts +147 -0
  356. package/src/components/release-compile.ts +445 -0
  357. package/src/components/release-gate.test.ts +215 -0
  358. package/src/components/release-gate.ts +341 -0
  359. package/src/components/release-promote.test.ts +159 -0
  360. package/src/components/release-promote.ts +232 -0
  361. package/src/components/run-progress.ts +7 -0
  362. package/src/components/wave-plan.ts +1 -1
  363. package/src/config.ts +7 -1
  364. package/src/index.ts +2 -0
  365. package/src/lifecycle/change-set-store.test.ts +217 -0
  366. package/src/lifecycle/change-set-store.ts +112 -0
  367. package/src/lifecycle/commit-sha.ts +23 -0
  368. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  369. package/src/lifecycle/gate-change-set.ts +133 -0
  370. package/src/lifecycle/gate-ledger.test.ts +33 -0
  371. package/src/lifecycle/gate-ledger.ts +66 -1
  372. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  373. package/src/lifecycle/release-cut.test.ts +411 -0
  374. package/src/lifecycle/release-cut.ts +367 -0
  375. package/src/lifecycle/release-ledger.test.ts +14 -0
  376. package/src/lifecycle/release-ledger.ts +85 -17
  377. package/src/lifecycle/release-store.ts +423 -0
  378. package/src/lifecycle/run-ledger.test.ts +10 -0
  379. package/src/lifecycle/run-ledger.ts +6 -0
  380. package/src/op/activities/change-coverage.ts +4 -1
  381. package/src/op/activities/source-release.ts +8 -3
  382. package/src/op/activities/source-rollback.ts +8 -4
  383. package/src/op/activity-registry.ts +7 -5
  384. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  385. package/src/op/discover-root.test.ts +39 -0
  386. package/src/op/discover.ts +15 -8
  387. package/src/op/gate-name.ts +13 -3
  388. package/src/op/gate-release.test.ts +126 -0
  389. package/src/op/gate-run-locator.test.ts +110 -0
  390. package/src/op/gate-run-locator.ts +35 -0
  391. package/src/op/gate.ts +88 -6
  392. package/src/op/local-executor.ts +207 -52
  393. package/src/op/op-waves-run.ts +4 -0
  394. package/src/op/run-events.test.ts +166 -0
  395. package/src/op/run-live.ts +1 -1
  396. package/src/op/runtime.ts +39 -0
  397. package/src/op/runtimes/local.test.ts +16 -0
  398. package/src/op/runtimes/local.ts +13 -2
  399. package/src/point-decision.ts +12 -2
  400. package/src/run-event-store.test.ts +144 -0
  401. package/src/run-event-store.ts +213 -0
  402. package/src/run-event-stream.test.ts +175 -0
  403. package/src/run-event-stream.ts +306 -0
  404. package/src/run-event.test.ts +277 -0
  405. package/src/run-event.ts +451 -0
  406. package/src/wave-loop-golden.test.ts +9 -0
  407. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  408. package/src/workspace/ancestry-index.test.ts +93 -0
  409. package/src/workspace/change-set-show.schema.json +102 -0
  410. package/src/workspace/change-set-show.ts +72 -0
  411. package/src/workspace/change-set.schema.json +28 -1
  412. package/src/workspace/changes-cli.ts +10 -1
  413. package/src/workspace/changes.schema.json +135 -0
  414. package/src/workspace/changes.test.ts +1 -1
  415. package/src/workspace/changes.ts +84 -2
  416. package/src/workspace/ci-green-cli.ts +75 -13
  417. package/src/workspace/ci-green-forge.ts +24 -4
  418. package/src/workspace/ci-green-runs.test.ts +242 -0
  419. package/src/workspace/ci-green-signed.test.ts +302 -0
  420. package/src/workspace/ci-green-workflow.test.ts +16 -0
  421. package/src/workspace/ci-green-workflow.ts +12 -0
  422. package/src/workspace/ci-green.test.ts +131 -2
  423. package/src/workspace/ci-green.ts +624 -42
  424. package/src/workspace/ci-last-green.schema.json +32 -2
  425. package/src/workspace/ci-list.schema.json +525 -0
  426. package/src/workspace/commit-links.test.ts +279 -0
  427. package/src/workspace/commit-links.ts +183 -0
  428. package/src/workspace/declaration.schema.json +12 -0
  429. package/src/workspace/declaration.ts +22 -2
  430. package/src/workspace/identity-gate-rule.ts +91 -7
  431. package/src/workspace/identity.ts +14 -1
  432. package/src/workspace/intent.ts +48 -1
  433. package/src/workspace/ls.schema.json +3 -1
  434. package/src/workspace/ls.ts +6 -0
  435. package/src/workspace/member-pipeline.test.ts +17 -0
  436. package/src/workspace/member-pipeline.ts +9 -0
  437. package/src/workspace/promotion-order.ts +97 -0
  438. package/src/workspace/read-contract.e2e.test.ts +4 -1
  439. package/src/workspace/reason-codes.test.ts +2 -1
  440. package/src/workspace/reason-codes.ts +4 -1
  441. package/src/workspace/releases.schema.json +391 -0
  442. package/src/workspace/releases.test.ts +108 -0
  443. package/src/workspace/releases.ts +122 -0
  444. package/src/workspace/run-event.schema.json +288 -0
  445. package/src/workspace/status-contract.test.ts +52 -1
  446. package/src/workspace/status-gates.ts +56 -1
  447. package/src/workspace/status-stewards.ts +6 -0
  448. package/src/workspace/status.schema.json +70 -1
  449. package/src/workspace/status.ts +80 -1
  450. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  451. package/src/workspace/trust/op-run-statement.ts +148 -0
  452. package/src/workspace/trust/rotation.ts +61 -14
  453. package/src/workspace/trust/seal.ts +10 -0
  454. package/src/workspace/trust/ssh-commit.ts +10 -9
  455. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  456. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  457. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  458. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  459. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -0,0 +1,194 @@
1
+ /**
2
+ * `chant components pr-plan` and `pr-apply` with `--events-json` (#4041,
3
+ * #4007): the planning as a `plan` phase, the gate's decision as a `gate`
4
+ * phase, and on pr-apply the waves and components of the apply, all on one
5
+ * schema-valid stream. Git, the change signal, the derivation and the gate
6
+ * ledger are fakes; the planning and the apply are the real ones.
7
+ */
8
+
9
+ import { mkdtemp, rm } from "node:fs/promises";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
13
+ import type { DriverComponent } from "../../components/driver";
14
+ import type { DeployContext } from "../../components/capability";
15
+ import type { ParsedArgs } from "../registry";
16
+
17
+ const HEAD = "a".repeat(40);
18
+ const BASE = "b".repeat(40);
19
+
20
+ vi.mock("../../components/changed-files", () => ({
21
+ resolveCommit: async () => HEAD,
22
+ resolveMergeBase: async () => BASE,
23
+ }));
24
+
25
+ vi.mock("../../config", async () => {
26
+ const actual = await vi.importActual<typeof import("../../config")>("../../config");
27
+ return { ...actual, loadChantConfig: async () => ({ config: {} }) };
28
+ });
29
+
30
+ vi.mock("./fan-out", () => ({
31
+ resolveChangedUnits: async () => ({ units: { changed: ["net"], indeterminate: [] } }),
32
+ }));
33
+
34
+ // #4022: pr-apply stores the change set its gate binds on chant/lifecycle and
35
+ // pushes it. These tests run in the chant checkout, so the store is replaced:
36
+ // it must never write or push the real repository's lifecycle branch.
37
+ vi.mock("../../lifecycle/gate-change-set", () => ({
38
+ storeGateChangeSet: async () => ({ written: false }),
39
+ readGraphIds: async () => ({ ids: [] }),
40
+ setGraphIdReader: () => {},
41
+ gateGraphReader: () => async () => ({ ids: [] }),
42
+ }));
43
+ vi.mock("../../lifecycle/git", async () => {
44
+ const actual = await vi.importActual<typeof import("../../lifecycle/git")>("../../lifecycle/git");
45
+ return { ...actual, pushLifecycleReport: async () => { throw new Error("pr.test must not push chant/lifecycle"); } };
46
+ });
47
+
48
+ vi.mock("./run", () => ({
49
+ GATED_EXIT_CODE: 3,
50
+ autoReleaseRun: (id: string) => ({ runId: id, runOrigin: { forge: "local" } }),
51
+ recordAutoReleasesForRun: async () => [],
52
+ }));
53
+
54
+ let gateLedger: ReturnType<typeof import("../../op/gate").memoryGateLedgerPort>;
55
+ vi.mock("../../op/gate", async () => {
56
+ const actual = await vi.importActual<typeof import("../../op/gate")>("../../op/gate");
57
+ return { ...actual, gitGateLedgerPort: () => gateLedger, gateIsSealed: async () => false };
58
+ });
59
+
60
+ const failing = new Set<string>();
61
+ vi.mock("../../components/fan-out-support", async () => {
62
+ const { CapabilityRegistry } = await import("../../components/capability");
63
+ const { planFanOut } = await import("../../components/fan-out");
64
+ const { computePlanDigest } = await import("../../lifecycle/plan-digest");
65
+ const registry = new CapabilityRegistry();
66
+ registry.register({
67
+ kind: "fake-root",
68
+ async plan(_ctx: DeployContext, input: { root: string }) {
69
+ const planDigest = computePlanDigest("fake-root", { root: input.root });
70
+ return {
71
+ member: input.root,
72
+ planDigest,
73
+ artifact: { planDigest },
74
+ changeSet: { member: { member: input.root, lexicon: "terraform", planner: "tofu", status: "planned", planDigest, holes: [] }, entries: [] },
75
+ };
76
+ },
77
+ async outputs() {
78
+ return {};
79
+ },
80
+ async run(_ctx: DeployContext, input: { root: string }) {
81
+ if (failing.has(input.root)) throw new Error(`${input.root} failed`);
82
+ return { outputs: {} };
83
+ },
84
+ } as never);
85
+ return {
86
+ fanOutRegistry: async () => registry,
87
+ deriveFanOut: async () => ({
88
+ success: true,
89
+ components: ESTATE,
90
+ plan: planFanOut({ components: ESTATE, changed: ["net"] }),
91
+ signal: { changed: ["net"], indeterminate: [], unclaimed: [] },
92
+ }),
93
+ };
94
+ });
95
+
96
+ const root = (name: string, dependsOn: string[] = []): DriverComponent => ({
97
+ name,
98
+ dependsOn,
99
+ deploy: [{ phase: "Apply", steps: [{ kind: "fake-root", root: name }] }],
100
+ });
101
+ const ESTATE: DriverComponent[] = [root("net"), root("app", ["net"])];
102
+
103
+ const { memoryGateLedgerPort } = await import("../../op/gate");
104
+ const { runComponentsPrApply, runComponentsPrPlan } = await import("./pr");
105
+ const { checkRunEventLines, stepTrail } = await import("../../workspace/__fixtures__/run-event-check");
106
+
107
+ let dir: string;
108
+ let out: string[];
109
+
110
+ const ctx = (a: Partial<ParsedArgs>) =>
111
+ ({ args: { command: "components", path: "pr-apply", format: "", fix: false, watch: false, verbose: false, help: false, live: false, base: "main", env: "prod", output: dir, eventsJson: true, ...a } as ParsedArgs, plugins: [] }) as never;
112
+
113
+ beforeEach(async () => {
114
+ dir = await mkdtemp(join(tmpdir(), "pr-events-"));
115
+ out = [];
116
+ failing.clear();
117
+ gateLedger = memoryGateLedgerPort();
118
+ vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array) => { out.push(String(chunk)); return true; });
119
+ vi.spyOn(console, "error").mockImplementation(() => {});
120
+ vi.spyOn(console, "log").mockImplementation(() => {});
121
+ });
122
+
123
+ afterEach(async () => {
124
+ vi.restoreAllMocks();
125
+ await rm(dir, { recursive: true, force: true });
126
+ });
127
+
128
+ /** The digest pr-plan printed, read back from its report. */
129
+ async function plannedDigest(): Promise<string> {
130
+ const { readFile } = await import("node:fs/promises");
131
+ return JSON.parse(await readFile(join(dir, "pr-plan.json"), "utf8")).digest as string;
132
+ }
133
+
134
+ describe("pr-plan --events-json", () => {
135
+ test("plans each component in a plan phase, then says where the approval stands; the run settles ok", async () => {
136
+ expect(await runComponentsPrPlan(ctx({ pr: 12 }))).toBe(0);
137
+ const events = checkRunEventLines(out);
138
+ expect(events[0]).toMatchObject({ type: "run-start", kind: "component", name: "pr-plan pr-12", waves: [["net"], ["app"]] });
139
+ expect(stepTrail(events)).toEqual([
140
+ ["step-start", "plan"], ["step-settle", "plan", "ok"],
141
+ ["step-start", "plan"], ["step-settle", "plan", "ok"],
142
+ ["step-start", "gate:pr-apply"], ["gate-waiting", "gate:pr-apply"], ["step-settle", "gate:pr-apply", "gated"],
143
+ ]);
144
+ expect(events.filter((e) => e.type === "step-start" && e.phase === "plan").map((e) => (e as { component: string }).component)).toEqual(["net", "app"]);
145
+ // The plan stage reads the ledger and writes nothing to it.
146
+ expect(gateLedger.appended).toEqual([]);
147
+ expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "ok" });
148
+ });
149
+ });
150
+
151
+ describe("pr-apply --events-json", () => {
152
+ test("refused: the gate phase settles gated with the reason, nothing applies, and the run is gated", async () => {
153
+ expect(await runComponentsPrApply(ctx({ pr: 12 }))).toBe(3);
154
+ const events = checkRunEventLines(out);
155
+ expect(events[0]).toMatchObject({ name: "pr-apply pr-12" });
156
+ expect(events.find((e) => e.type === "gate-waiting")).toMatchObject({ op: "pr-12", gate: "pr-apply", message: expect.stringMatching(/No approval stands for this plan/) });
157
+ expect(events.some((e) => e.type === "wave-start")).toBe(false);
158
+ expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "gated" });
159
+ });
160
+
161
+ test("approved: the gate resolves, then each wave and component applies; a failure settles the run failed", async () => {
162
+ await runComponentsPrPlan(ctx({ pr: 12 }));
163
+ const digest = await plannedDigest();
164
+ gateLedger = memoryGateLedgerPort({
165
+ resolutions: [{ version: 1, kind: "resolution", op: "pr-12", gate: "pr-apply", resolvedBy: "github:alice", timestamp: new Date().toISOString(), planDigest: digest }],
166
+ });
167
+ out = [];
168
+ failing.add("app");
169
+ expect(await runComponentsPrApply(ctx({ pr: 12 }))).toBe(1);
170
+ const events = checkRunEventLines(out);
171
+ expect(events.map((e) => e.type).filter((t) => t !== "step-start" && t !== "step-settle")).toEqual([
172
+ "run-start",
173
+ "phase-start", "phase-settle",
174
+ "phase-start", "gate-resolved", "phase-settle",
175
+ "wave-start", "component-start", "phase-start", "phase-settle", "component-settle", "wave-settle",
176
+ "wave-start", "component-start", "phase-start", "phase-settle", "component-settle", "wave-settle",
177
+ "run-settle",
178
+ ]);
179
+ expect(events.find((e) => e.type === "gate-resolved")).toMatchObject({ resolvedBy: "github:alice" });
180
+ expect(events.find((e) => e.type === "component-settle" && e.component === "app")).toMatchObject({ status: "failed" });
181
+ expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "failed" });
182
+ });
183
+ });
184
+
185
+ describe("--events-json beside --json", () => {
186
+ test("is refused on pr-plan and pr-apply before anything plans, since both print to stdout", async () => {
187
+ const errors: string[] = [];
188
+ vi.spyOn(console, "error").mockImplementation((...m: unknown[]) => { errors.push(m.join(" ")); });
189
+ expect(await runComponentsPrPlan(ctx({ pr: 12, json: true }))).toBe(1);
190
+ expect(await runComponentsPrApply(ctx({ pr: 12, json: true }))).toBe(1);
191
+ expect(errors.filter((e) => /--events-json and --json both stream to stdout/.test(e))).toHaveLength(2);
192
+ expect(out).toEqual([]);
193
+ });
194
+ });
@@ -29,6 +29,12 @@
29
29
  *
30
30
  * Every run writes `pr-plan.json` or `pr-apply.json` (`pr-report.schema.json`),
31
31
  * `change-set.json` and `pr-note.md` under `--output` (default `.chant/pr`). *
32
+ * `--events-json` (#4041, #4007) streams the stage's run events to stdout,
33
+ * one `run-event.schema.json` line each: the planning as a `plan` phase with
34
+ * a step per component, the gate's decision as a `gate` phase, and on
35
+ * pr-apply the apply's waves and components as `chant run --components`
36
+ * streams them.
37
+ *
32
38
  * `--member <name>` (#3465) runs a workspace member's loop: the member's
33
39
  * generated pipeline passes it from the member's directory. The gate is
34
40
  * recorded under `pr-<n>-<member>`, and the note and the statuses carry the
@@ -54,7 +60,10 @@ import {
54
60
  } from "../../components/pr-run";
55
61
  import { readFanOutAttempt, writeFanOutAttempt, type FanOutAttempt, type PrApplyRecord } from "../../components/fan-out-record";
56
62
  import { samePlanDigest } from "../../lifecycle/plan-digest";
57
- import { gitGateLedgerPort } from "../../op/gate";
63
+ import { gitGateLedgerPort, type GateCheck } from "../../op/gate";
64
+ import { emitGateStep, gateEventsOf, GATE_PHASE, type RunEventSink, type RunEventStream } from "../../run-event-stream";
65
+ import type { RunEventSettleStatus } from "../../run-event";
66
+ import { openCliRunEvents, refusesEventsWithJson } from "../run-events";
58
67
  import {
59
68
  describePlanChanged,
60
69
  PR_APPLY_GATE,
@@ -74,7 +83,7 @@ import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
74
83
  import { formatError, formatInfo, formatSuccess, formatWarning } from "../format";
75
84
  import { findWorkspaceRoot } from "../../project-root";
76
85
  import { resolveChangedUnits } from "./fan-out";
77
- import { GATED_EXIT_CODE, recordAutoReleasesForRun } from "./run";
86
+ import { GATED_EXIT_CODE, autoReleaseRun, recordAutoReleasesForRun } from "./run";
78
87
  import type { CommandContext } from "../registry";
79
88
  import type { ComponentChangeSignal, FanOutPlan } from "../../components/fan-out";
80
89
  import type { DriverComponent } from "../../components/driver";
@@ -225,8 +234,32 @@ async function checkMember(projectPath: string, names: string[]): Promise<string
225
234
  }
226
235
 
227
236
  /** Plan every member the derivation selects. */
228
- function planAll(p: Prepared): Promise<PrPlanSet> {
229
- return planPrSet({ components: p.components, plan: p.plan, registry: p.registry, env: p.env, seededOutputs: p.seededOutputs });
237
+ function planAll(p: Prepared, onRunEvent?: RunEventSink): Promise<PrPlanSet> {
238
+ return planPrSet({
239
+ components: p.components,
240
+ plan: p.plan,
241
+ registry: p.registry,
242
+ env: p.env,
243
+ seededOutputs: p.seededOutputs,
244
+ ...(onRunEvent ? { onRunEvent } : {}),
245
+ });
246
+ }
247
+
248
+ /**
249
+ * How a stage's report settles its run's events (#4041): a refusal waits on
250
+ * a person, as a gate does, and exits the same way.
251
+ */
252
+ function settleOf(status: PrReport["status"]): RunEventSettleStatus {
253
+ switch (status) {
254
+ case "nothing":
255
+ case "planned":
256
+ case "applied":
257
+ return "ok";
258
+ case "refused":
259
+ return "gated";
260
+ default:
261
+ return "failed";
262
+ }
230
263
  }
231
264
 
232
265
  /** Write the report, the change set and the note; print the note's path. */
@@ -284,20 +317,50 @@ async function finish(ctx: CommandContext, p: Prepared, report: PrReport): Promi
284
317
  if (ctx.args.json) console.log(JSON.stringify(report, null, 2));
285
318
  }
286
319
 
287
- /** chant components pr-plan --base <ref> --pr <n> [--env <env>] [--gate <name>] [--forge <kind>] [--output <dir>] [--json] */
288
- export async function runComponentsPrPlan(ctx: CommandContext): Promise<number> {
320
+ /** A stage's stream, held so that a stage that returns or throws before it settles still ends it. */
321
+ interface StageEvents {
322
+ stream?: RunEventStream;
323
+ }
324
+
325
+ /** Run a stage, and end its stream as `failed` when the stage did not settle it. */
326
+ async function withStageEvents(stage: string, run: (events: StageEvents) => Promise<number>): Promise<number> {
327
+ const events: StageEvents = {};
328
+ try {
329
+ return await run(events);
330
+ } catch (err) {
331
+ events.stream?.settle("failed", err instanceof Error ? err.message : String(err));
332
+ throw err;
333
+ } finally {
334
+ events.stream?.settle("failed", `${stage} stopped before it finished`);
335
+ }
336
+ }
337
+
338
+ /** chant components pr-plan --base <ref> --pr <n> [--env <env>] [--gate <name>] [--forge <kind>] [--output <dir>] [--json] [--events-json] */
339
+ export function runComponentsPrPlan(ctx: CommandContext): Promise<number> {
340
+ if (refusesEventsWithJson(ctx.args, "the stage's report")) return Promise.resolve(1);
341
+ return withStageEvents("pr-plan", (events) => prPlan(ctx, events));
342
+ }
343
+
344
+ async function prPlan(ctx: CommandContext, stage: StageEvents): Promise<number> {
289
345
  if (ctx.args.pr === undefined) {
290
346
  return fail("pr-plan needs the pull or merge request: --pr <n>", "Its approval is recorded under op pr-<n>, so one pull request's approval never answers another's.");
291
347
  }
292
348
  const p = await prepare(ctx, "plan");
293
349
  if (typeof p === "number") return p;
294
- const set = await planAll(p);
295
350
  const op = prOp(ctx.args.pr, p.member);
351
+ // `--events-json` (#4041): planning, then where the approval stands.
352
+ const events = await openCliRunEvents(ctx.args, { kind: "component", name: `pr-plan ${op}`, cwd: resolve(".") });
353
+ stage.stream = events;
354
+ events?.emit({ type: "run-start", waves: p.plan.waves });
355
+ const set = await planAll(p, events?.emit);
296
356
 
297
357
  let approval: PrApproval = { status: "pending" };
298
358
  if (p.plan.order.length > 0) {
299
359
  try {
300
- approval = approvalOf(await decidePrGate(readOnlyLedger(gitGateLedgerPort()), { op, gate: p.gate, digest: set.doc.digest }));
360
+ const check = await emitGateStep(events?.emit, { op, gate: p.gate }, () =>
361
+ decidePrGate(readOnlyLedger(gitGateLedgerPort()), { op, gate: p.gate, digest: set.doc.digest }),
362
+ );
363
+ approval = approvalOf(check);
301
364
  } catch (err) {
302
365
  console.error(formatWarning({ message: `could not read the gate ledger, so the approval reads as pending: ${err instanceof Error ? err.message : String(err)}` }));
303
366
  }
@@ -319,6 +382,8 @@ export async function runComponentsPrPlan(ctx: CommandContext): Promise<number>
319
382
  status,
320
383
  });
321
384
  await finish(ctx, p, report);
385
+ // The plan stage settles on its planning: an approval still to come is what it reports, not a stop.
386
+ events?.settle(settleOf(status), status === "plan-failed" ? "a member failed to plan" : undefined);
322
387
 
323
388
  console.error(formatInfo(`pr-plan: ${report.members.length} member(s), ${status}`));
324
389
  printMembers(report);
@@ -329,8 +394,13 @@ export async function runComponentsPrPlan(ctx: CommandContext): Promise<number>
329
394
  return status === "plan-failed" ? 1 : 0;
330
395
  }
331
396
 
332
- /** chant components pr-apply --base <ref> [--pr <n>] [--env <env>] [--gate <name>] [--forge <kind>] [--require-review] [--output <dir>] [--json] */
333
- export async function runComponentsPrApply(ctx: CommandContext): Promise<number> {
397
+ /** chant components pr-apply --base <ref> [--pr <n>] [--env <env>] [--gate <name>] [--forge <kind>] [--require-review] [--output <dir>] [--json] [--events-json] */
398
+ export function runComponentsPrApply(ctx: CommandContext): Promise<number> {
399
+ if (refusesEventsWithJson(ctx.args, "the stage's report")) return Promise.resolve(1);
400
+ return withStageEvents("pr-apply", (events) => prApply(ctx, events));
401
+ }
402
+
403
+ async function prApply(ctx: CommandContext, stage: StageEvents): Promise<number> {
334
404
  const { args } = ctx;
335
405
  if (args.requireReview && !args.forge) return fail("--require-review reads the pull request's reviews, so it needs --forge <kind>");
336
406
  const p = await prepare(ctx, "apply");
@@ -345,6 +415,12 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
345
415
  }
346
416
  }
347
417
  const op = pr === null ? "pr-unknown" : prOp(pr, p.member);
418
+ // The run id the release records carry, chosen before the run so that
419
+ // `--events-json` (#4041, #4007) names the run by it too.
420
+ const release = autoReleaseRun(`${op}-${Date.now()}`);
421
+ const events = await openCliRunEvents(args, { kind: "component", name: `pr-apply ${op}`, runId: release.runId, cwd: resolve(".") });
422
+ stage.stream = events;
423
+ events?.emit({ type: "run-start", waves: p.plan.waves });
348
424
 
349
425
  // `--resume` (#3464): an earlier attempt at this change on this commit
350
426
  // applied part of an approved set. When the members left are covered by
@@ -360,10 +436,12 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
360
436
  }
361
437
  const resumed = pr !== null && p.plan.order.length > 0 && attempt ? await tryResume(p, attempt, args.resume!, op) : undefined;
362
438
 
363
- const set = resumed?.set ?? (await planAll(p));
439
+ if (resumed) emitResumedGate(events, op, p.gate, resumed.check);
440
+ const set = resumed?.set ?? (await planAll(p, events?.emit));
364
441
  const base = { stage: "apply" as const, pr, env: p.env, ...(p.member ? { member: p.member } : {}), base: p.base, head: p.head, op, gate: p.gate, plan: p.plan, signal: p.signal, set };
365
442
 
366
- const stop = async (report: PrReport, code: number): Promise<number> => {
443
+ const stop = async (report: PrReport, code: number, settle?: RunEventSettleStatus): Promise<number> => {
444
+ events?.settle(settle ?? settleOf(report.status), report.status === "plan-failed" || report.status === "failed" ? report.message ?? `pr-apply ${report.status}` : undefined);
367
445
  await finish(ctx, p, report);
368
446
  console.error((code === 0 ? formatInfo : (m: string) => formatWarning({ message: m }))(`pr-apply: ${report.status}${report.refusal ? ` (${report.refusal})` : ""}`));
369
447
  if (report.message) console.error(report.message);
@@ -398,16 +476,32 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
398
476
  );
399
477
  }
400
478
 
479
+ // #4022: keep the change set the gate binds on chant/lifecycle under its
480
+ // digest before deciding the gate, so the plan an approval bound, or the
481
+ // one a pending fact waits on, can be read once this job is gone.
482
+ await storeChangeSetForGate(ctx, p, set.doc);
483
+
401
484
  let approval: PrApproval;
402
485
  if (resumed) {
403
486
  approval = resumed.approval;
404
487
  } else {
405
- const check = await decidePrGate(gitGateLedgerPort(), {
406
- op,
407
- gate: p.gate,
408
- digest: set.doc.digest,
409
- description: `pull request #${pr}: ${p.plan.order.join(", ")}`,
410
- });
488
+ const check = await emitGateStep(
489
+ events?.emit,
490
+ { op, gate: p.gate },
491
+ () =>
492
+ decidePrGate(gitGateLedgerPort(), {
493
+ op,
494
+ gate: p.gate,
495
+ digest: set.doc.digest,
496
+ description: `pull request #${pr}: ${p.plan.order.join(", ")}`,
497
+ }),
498
+ (unapproved) => {
499
+ const a = approvalOf(unapproved);
500
+ return a.status === "changed"
501
+ ? describePlanChanged({ op, gate: p.gate, digest: set.doc.digest, approval: a })
502
+ : `No approval stands for this plan. Approve it: ${prApproveCommand({ op, gate: p.gate, digest: set.doc.digest })}`;
503
+ },
504
+ );
411
505
  approval = approvalOf(check);
412
506
  if (!check.satisfied) {
413
507
  const partial = { ...base, approval, status: "refused" as const };
@@ -431,19 +525,12 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
431
525
  }
432
526
  const strangers = approversWithoutReview(approval.approvedBy ?? [], reviewers);
433
527
  if (strangers.length > 0) {
434
- return stop(
435
- prReport({
436
- ...base,
437
- approval,
438
- status: "refused",
439
- refusal: "not-a-reviewer",
440
- message:
441
- `The plan is approved by ${strangers.join(", ")}, who ${strangers.length === 1 ? "has" : "have"} no approving review on #${pr}` +
442
- ` (approving reviews: ${reviewers.join(", ") || "none"}). Nothing was applied. ` +
443
- `An approval counts when it names the reviewer the way the forge does: ${p.forge.principalOf("<login>")}.`,
444
- }),
445
- GATED_EXIT_CODE,
446
- );
528
+ const message =
529
+ `The plan is approved by ${strangers.join(", ")}, who ${strangers.length === 1 ? "has" : "have"} no approving review on #${pr}` +
530
+ ` (approving reviews: ${reviewers.join(", ") || "none"}). Nothing was applied. ` +
531
+ `An approval counts when it names the reviewer the way the forge does: ${p.forge.principalOf("<login>")}.`;
532
+ emitReviewStop(events, message);
533
+ return stop(prReport({ ...base, approval, status: "refused", refusal: "not-a-reviewer", message }), GATED_EXIT_CODE);
447
534
  }
448
535
  }
449
536
 
@@ -482,6 +569,8 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
482
569
  env: p.env,
483
570
  set,
484
571
  ...(resumed ? { resumed: prior } : {}),
572
+ // The apply is this run's waves, so the fan-out's own run framing is dropped.
573
+ ...(events ? { onRunEvent: events.nested } : {}),
485
574
  ...(resumePath
486
575
  ? {
487
576
  onComponentSettled: (settled, outputs) => {
@@ -497,7 +586,7 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
497
586
  : {}),
498
587
  });
499
588
  recordAttempt();
500
- await recordAutoReleasesForRun(applied.run.results, p.env, `${op}-${Date.now()}`, resolveAutoReleaseDisabled(p.config, args.noReleaseRecord));
589
+ await recordAutoReleasesForRun(applied.run.results, p.env, release.runId, resolveAutoReleaseDisabled(p.config, args.noReleaseRecord), release.runOrigin);
501
590
  const report = prReport({
502
591
  ...base,
503
592
  approval,
@@ -505,14 +594,59 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
505
594
  members: applied.members,
506
595
  ...(resumed ? { resumed: resumed.completed } : {}),
507
596
  });
508
- const code = await stop(report, applied.status === "applied" ? 0 : 1);
597
+ // A component that stopped at a gate of its own leaves the apply waiting on a person.
598
+ const code = await stop(report, applied.status === "applied" ? 0 : 1, applied.run.status === "gated" ? "gated" : undefined);
509
599
  if (applied.status === "applied") console.error(formatSuccess(`applied the plan ${report.digest} approved by ${(approval.approvedBy ?? []).join(", ")}`));
510
600
  return code;
511
601
  }
512
602
 
603
+ /** A resumed apply's gate (#3464), decided against the approved digest, as the run's `gate` phase. */
604
+ function emitResumedGate(events: RunEventStream | undefined, op: string, gate: string, check: GateCheck): void {
605
+ if (!events) return;
606
+ const where = { component: null, phase: GATE_PHASE, step: `gate:${gate}` };
607
+ events.emit({ type: "phase-start", component: null, phase: GATE_PHASE });
608
+ events.emit({ type: "step-start", ...where });
609
+ for (const body of gateEventsOf(check, { ...where, op, gate }, { durationMs: null })) events.emit(body);
610
+ events.emit({ type: "phase-settle", component: null, phase: GATE_PHASE, status: check.satisfied ? "ok" : "gated" });
611
+ }
612
+
613
+ /** An approval by someone with no approving review (#3183) stops the apply: a `review` phase that settles `gated`. */
614
+ function emitReviewStop(events: RunEventStream | undefined, message: string): void {
615
+ if (!events) return;
616
+ events.emit({ type: "phase-start", component: null, phase: "review" });
617
+ events.emit({ type: "step-start", component: null, phase: "review", step: "review" });
618
+ events.emit({ type: "step-settle", component: null, phase: "review", step: "review", status: "gated", durationMs: null, message });
619
+ events.emit({ type: "phase-settle", component: null, phase: "review", status: "gated" });
620
+ }
621
+
622
+ /**
623
+ * Store `doc` under its digest with its entries mapped onto the member's
624
+ * graph (`storeGateChangeSet`), and push. A failure is a warning: the gate
625
+ * decides the same either way.
626
+ */
627
+ async function storeChangeSetForGate(ctx: CommandContext, p: Prepared, doc: PrPlanSet["doc"]): Promise<void> {
628
+ const { storeGateChangeSet, gateGraphReader } = await import("../../lifecycle/gate-change-set");
629
+ const { pushLifecycleReport } = await import("../../lifecycle/git");
630
+ const graphArgs = [
631
+ ...(ctx.args.param ?? []).flatMap((kv) => ["--param", kv]),
632
+ ...(ctx.args.paramsFile ? ["--params-file", ctx.args.paramsFile] : []),
633
+ ];
634
+ try {
635
+ const stored = await storeGateChangeSet({ doc, cwd: resolve("."), at: p.head, readGraph: gateGraphReader(graphArgs) });
636
+ if (stored.warning) console.error(formatWarning({ message: stored.warning }));
637
+ // The push's own outcome is said by the gate's pending fact, or by the
638
+ // release records the apply writes, which push the same branch.
639
+ if (stored.written) await pushLifecycleReport();
640
+ } catch (err) {
641
+ console.error(formatWarning({ message: `could not store the change set ${doc.digest}: ${err instanceof Error ? err.message : String(err)}` }));
642
+ }
643
+ }
644
+
513
645
  interface Resumed {
514
646
  plan: FanOutPlan;
515
647
  set: PrPlanSet;
648
+ /** The gate's decision against the approved digest. */
649
+ check: GateCheck;
516
650
  approval: PrApproval;
517
651
  completed: string[];
518
652
  outputs: Record<string, Record<string, unknown>>;
@@ -557,5 +691,5 @@ async function tryResume(p: Prepared, attempt: FanOutAttempt, file: string, op:
557
691
  });
558
692
  if (!resumed.ok) return skip(`what is left is not what was approved. ${resumed.reasons.join("; ")}`);
559
693
  console.error(formatInfo(`resume : ${attempt.completed.join(", ")} applied in an earlier attempt; applying ${resumed.plan.order.join(", ") || "nothing more"} under the approval of ${record.digest}`));
560
- return { plan: resumed.plan, set: resumed.set, approval: approvalOf(check), completed: attempt.completed, outputs };
694
+ return { plan: resumed.plan, set: resumed.set, check, approval: approvalOf(check), completed: attempt.completed, outputs };
561
695
  }