@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
@@ -52,6 +52,7 @@ import { remainingFanOut, type ComponentChangeSignal, type FanOutPlan } from "./
52
52
  import type { PrApplyRecord } from "./fan-out-record";
53
53
  import type { CapabilityRegistry } from "./capability";
54
54
  import type { RunProgressEvent } from "./run-progress";
55
+ import type { RunEventSink } from "../run-event-stream";
55
56
  import { planWaveComponent, readComponentOutputs } from "./wave-plan";
56
57
 
57
58
  export interface PrPlanSetOptions {
@@ -64,8 +65,17 @@ export interface PrPlanSetOptions {
64
65
  vars?: Record<string, unknown>;
65
66
  /** Outputs supplied by the caller (`--seed-outputs`). They win over outputs read here. */
66
67
  seededOutputs?: Record<string, Record<string, unknown>>;
68
+ /**
69
+ * Where the planning's events go (#4041): a `plan` phase with no
70
+ * component, and in it one `plan` step per component, started as its wave
71
+ * is planned and settled `ok` or `failed`.
72
+ */
73
+ onRunEvent?: RunEventSink;
67
74
  }
68
75
 
76
+ /** The phase a pull request's planning is, in its run's events. */
77
+ export const PR_PLAN_PHASE = "plan";
78
+
69
79
  /** Every member of a pull request, planned. */
70
80
  export interface PrPlanSet {
71
81
  /** The change-set document over every member. Its digest is what the gate binds. */
@@ -101,6 +111,8 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
101
111
  const componentOf = new Map<string, string>();
102
112
  const plans: Record<string, Record<string, unknown>> = {};
103
113
  let failed = false;
114
+ const emit = options.onRunEvent;
115
+ emit?.({ type: "phase-start", component: null, phase: PR_PLAN_PHASE });
104
116
 
105
117
  for (const wave of options.plan.waves) {
106
118
  // The dependencies' outputs first, each read once. A dependency that
@@ -115,7 +127,20 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
115
127
  }
116
128
  }
117
129
  const planned = await Promise.all(
118
- wave.map((name) => planWaveComponent(byName.get(name)!, options.registry, deployContext(options, name), outputs)),
130
+ wave.map(async (name) => {
131
+ const at = { component: name, phase: PR_PLAN_PHASE, step: "plan" };
132
+ emit?.({ type: "step-start", ...at });
133
+ const start = Date.now();
134
+ const p = await planWaveComponent(byName.get(name)!, options.registry, deployContext(options, name), outputs);
135
+ emit?.({
136
+ type: "step-settle",
137
+ ...at,
138
+ status: "error" in p ? "failed" : "ok",
139
+ durationMs: Date.now() - start,
140
+ ...("error" in p ? { error: p.error } : {}),
141
+ });
142
+ return p;
143
+ }),
119
144
  );
120
145
  for (const p of planned) {
121
146
  const component = byName.get(p.component)!;
@@ -135,6 +160,7 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
135
160
  }
136
161
  }
137
162
 
163
+ emit?.({ type: "phase-settle", component: null, phase: PR_PLAN_PHASE, status: failed ? "failed" : "ok" });
138
164
  const doc = composeChangeSet(parts);
139
165
  const members: PrMember[] = doc.members.map((m) => ({
140
166
  member: m.member,
@@ -206,6 +232,8 @@ export interface PrApplyOptions {
206
232
  /** The set {@link planPrSet} planned and the gate approved, or {@link resumePrSet}'s set. */
207
233
  set: PrPlanSet;
208
234
  onProgress?: (event: RunProgressEvent) => void;
235
+ /** Where the apply's events go (#4041): the fan-out's run events. */
236
+ onRunEvent?: RunEventSink;
209
237
  /**
210
238
  * What an earlier attempt at this approved set applied (#3464): its
211
239
  * components, which are reported applied and not run again, and the
@@ -230,6 +258,7 @@ export async function applyPrSet(options: PrApplyOptions): Promise<{ members: Pr
230
258
  plans: options.set.plans,
231
259
  componentOutputs: { ...before },
232
260
  ...(options.onProgress ? { onProgress: options.onProgress } : {}),
261
+ ...(options.onRunEvent ? { onRunEvent: options.onRunEvent } : {}),
233
262
  ...(options.onComponentSettled ? { onComponentSettled: options.onComponentSettled } : {}),
234
263
  });
235
264
  const completed = new Set(run.completed);
@@ -0,0 +1,90 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { promotionOrder, staticPromotionOrder } from "../workspace/promotion-order";
3
+ import { resolvePromotionSource } from "./promote-order";
4
+
5
+ describe("promotionOrder (#4030)", () => {
6
+ test("is the literal entries in order, with canary read from the object form", () => {
7
+ expect(promotionOrder(["dev", { name: "staging", canary: true }, { name: "prod", endpoint: "http://x" }, "pr-*"])).toEqual([
8
+ { name: "dev", canary: false },
9
+ { name: "staging", canary: true },
10
+ { name: "prod", canary: false },
11
+ ]);
12
+ });
13
+
14
+ test("is empty without environments, and a repeated name keeps its first place", () => {
15
+ expect(promotionOrder(undefined)).toEqual([]);
16
+ expect(promotionOrder(null)).toEqual([]);
17
+ expect(promotionOrder(["a", "b", "a"]).map((s) => s.name)).toEqual(["a", "b"]);
18
+ });
19
+ });
20
+
21
+ describe("resolvePromotionSource (#4030)", () => {
22
+ const order = promotionOrder(["dev", "staging", "prod"]);
23
+
24
+ test("defaults from to the step before to", () => {
25
+ expect(resolvePromotionSource({ order, to: "prod" })).toEqual({ from: "staging" });
26
+ expect(resolvePromotionSource({ order, to: "staging" })).toEqual({ from: "dev" });
27
+ });
28
+
29
+ test("an adjacent from is accepted", () => {
30
+ expect(resolvePromotionSource({ order, to: "prod", from: "staging" })).toEqual({ from: "staging" });
31
+ });
32
+
33
+ test("a from that skips a step is refused, and allowSkip lifts it", () => {
34
+ const r = resolvePromotionSource({ order, to: "prod", from: "dev" });
35
+ expect(r).toMatchObject({ error: { code: "promote-skips-environment" } });
36
+ expect("error" in r && r.error.message).toContain("skips staging");
37
+ expect("error" in r && r.error.fix).toContain("--allow-skip");
38
+ expect(resolvePromotionSource({ order, to: "prod", from: "dev", allowSkip: true })).toEqual({ from: "dev" });
39
+ });
40
+
41
+ test("a step outside the order is not judged", () => {
42
+ expect(resolvePromotionSource({ order, to: "prod", from: "hotfix" })).toEqual({ from: "hotfix" });
43
+ expect(resolvePromotionSource({ order, to: "pr-42", from: "dev" })).toEqual({ from: "dev" });
44
+ });
45
+
46
+ test("going backwards is not a skip", () => {
47
+ expect(resolvePromotionSource({ order, to: "dev", from: "prod" })).toEqual({ from: "prod" });
48
+ });
49
+
50
+ test("no order leaves from as given", () => {
51
+ expect(resolvePromotionSource({ order: [], to: "prod" })).toEqual({ from: undefined });
52
+ expect(resolvePromotionSource({ order: [], to: "prod", from: "dev" })).toEqual({ from: "dev" });
53
+ });
54
+ });
55
+
56
+ describe("staticPromotionOrder (#4030)", () => {
57
+ test("reads literal strings and objects without running the config, comments left out", () => {
58
+ const src = `// environments: ["old"]
59
+ import type { ChantConfig } from "@intentius/chant";
60
+ export default {
61
+ lexicons: ["aws"],
62
+ /* the order */
63
+ environments: [
64
+ "dev",
65
+ { name: "staging", canary: true },
66
+ { name: 'prod', endpoint: "http://localhost:4566" },
67
+ "pr-*",
68
+ ],
69
+ } satisfies ChantConfig;
70
+ `;
71
+ expect(staticPromotionOrder(src)).toEqual([
72
+ { name: "dev", canary: false },
73
+ { name: "staging", canary: true },
74
+ { name: "prod", canary: false },
75
+ ]);
76
+ });
77
+
78
+ test("a config with no environments and no spread declares none", () => {
79
+ expect(staticPromotionOrder('export default { lexicons: ["aws"] };\n')).toEqual([]);
80
+ });
81
+
82
+ test("anything computed is left to the loader", () => {
83
+ expect(staticPromotionOrder('export default { environments: [...base, "prod"] };')).toBeUndefined();
84
+ expect(staticPromotionOrder('const envs = ["a"];\nexport default { environments: envs };')).toBeUndefined();
85
+ expect(staticPromotionOrder('export default { environments: ["a", `b${x}`] };')).toBeUndefined();
86
+ expect(staticPromotionOrder('export default { environments: ["a", { name: n }] };')).toBeUndefined();
87
+ expect(staticPromotionOrder('export default { a: { environments: ["x"] }, environments: ["y"] };')).toBeUndefined();
88
+ expect(staticPromotionOrder("export default makeConfig();")).toBeUndefined();
89
+ });
90
+ });
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The environment `chant components promote` reads from, given the member's
3
+ * promotion order (#4030, see `../workspace/promotion-order.ts`). These
4
+ * refusals are promote's own and are not codes of the workspace read contract.
5
+ */
6
+
7
+ import type { PromotionStep } from "../workspace/promotion-order";
8
+
9
+ /** The refusals `components promote` gives for the declared order. Stable: scripts may match on them. */
10
+ export const PROMOTE_ORDER_CODES = [
11
+ /** `--from` is more than one step behind `--to` in the declared order, and `--allow-skip` was not given. */
12
+ "promote-skips-environment",
13
+ /** `--to` is the first step of the declared order, so no environment comes before it to default `--from` to. */
14
+ "promote-no-previous-environment",
15
+ /** `--from` was left out and `--to` is not a step of the declared order, so there is nothing to default it from. */
16
+ "promote-to-not-in-order",
17
+ ] as const;
18
+ export type PromoteOrderCode = (typeof PROMOTE_ORDER_CODES)[number];
19
+
20
+ export interface PromotionRefusal {
21
+ error: { code: PromoteOrderCode; message: string; fix: string };
22
+ }
23
+
24
+ /**
25
+ * The environment a promote reads from. With an order declared, `--from`
26
+ * defaults to the step before `to`, and a `--from` that skips a step is
27
+ * refused unless `allowSkip`. Without an order, or when `to` or `from` is not
28
+ * a step of it, the order has nothing to say and `from` is returned as given
29
+ * (`undefined` is the caller's to refuse, as before).
30
+ */
31
+ export function resolvePromotionSource(opts: {
32
+ order: readonly PromotionStep[];
33
+ to: string;
34
+ from?: string;
35
+ allowSkip?: boolean;
36
+ }): { from: string | undefined } | PromotionRefusal {
37
+ const { order, to, from, allowSkip } = opts;
38
+ const names = order.map((s) => s.name);
39
+ const list = names.join(", ");
40
+ const toAt = names.indexOf(to);
41
+ if (from === undefined) {
42
+ if (order.length === 0) return { from: undefined };
43
+ if (toAt < 0) {
44
+ return {
45
+ error: {
46
+ code: "promote-to-not-in-order",
47
+ message: `--from was not given, and "${to}" is not a step of the declared promotion order (${list})`,
48
+ fix: `Pass --from <env>, or add "${to}" to the environments array of this member's chant.config.ts.`,
49
+ },
50
+ };
51
+ }
52
+ if (toAt === 0) {
53
+ return {
54
+ error: {
55
+ code: "promote-no-previous-environment",
56
+ message: `"${to}" is the first step of the declared promotion order (${list}), so there is no environment to promote from`,
57
+ fix: `Deploy "${to}" with chant run --env ${to}, or pass --from <env> to promote from an environment outside the order.`,
58
+ },
59
+ };
60
+ }
61
+ return { from: names[toAt - 1] };
62
+ }
63
+ const fromAt = names.indexOf(from);
64
+ if (toAt >= 0 && fromAt >= 0 && fromAt < toAt - 1 && !allowSkip) {
65
+ const skipped = names.slice(fromAt + 1, toAt);
66
+ return {
67
+ error: {
68
+ code: "promote-skips-environment",
69
+ message: `promoting ${from} to ${to} skips ${skipped.join(", ")} in the declared promotion order (${list})`,
70
+ fix: `Promote to ${skipped[0]} first, or pass --allow-skip to promote ${from} to ${to} anyway.`,
71
+ },
72
+ };
73
+ }
74
+ return { from };
75
+ }
@@ -2,7 +2,7 @@
2
2
  * Promote a release without rebuilding (#2530).
3
3
  */
4
4
 
5
- import { describe, test, expect } from "vitest";
5
+ import { describe, test, expect, vi } from "vitest";
6
6
  import { CapabilityRegistry, type DeployContext } from "./capability";
7
7
  import { describeGateMismatch, memoryGateLedgerPort, type GateLedgerPort } from "../op/gate";
8
8
  import { componentPlanDigest } from "./gate-plan";
@@ -20,6 +20,7 @@ import {
20
20
  runPromotion,
21
21
  promotionRecord,
22
22
  gateApprover,
23
+ gateAdmission,
23
24
  promoteArchivePaths,
24
25
  hasPublishStep,
25
26
  parseDigestPins,
@@ -282,10 +283,15 @@ describe("running a promotion", () => {
282
283
  const { registry, ran } = fakeRegistry({ api: "sha256:api" });
283
284
  const opts = { plan: planFor("api", "sha256:api"), components: [prepared(gated)], registry, gates };
284
285
 
286
+ vi.stubEnv("GITHUB_RUN_ID", "9001");
287
+ vi.stubEnv("GITHUB_SHA", "e".repeat(40));
285
288
  const first = await runPromotion({ ...opts, now: "2026-01-03T00:00:00Z" });
286
289
  expect(first.status).toBe("gated");
287
290
  expect(first.gate?.gate).toBe("prod-release");
288
291
  expect(ran).not.toContain("prod:api:apply");
292
+ // #4020: the pending fact of a component gate names the CI run, and a commit.
293
+ expect(first.gate?.runId).toBe("9001");
294
+ expect(first.gate?.gitSha).toMatch(/^[0-9a-f]{40}$/);
289
295
 
290
296
  // `chant approve` copies the pending fact's environment and plan (#2574).
291
297
  const bound = { planDigest: first.gate!.planDigest!, environment: first.gate!.environment! };
@@ -294,6 +300,9 @@ describe("running a promotion", () => {
294
300
  const second = await runPromotion({ ...opts, now: "2026-01-03T00:02:00Z" });
295
301
  expect(second.status).toBe("ok");
296
302
  expect(gateApprover(second.results[0])).toBe("alice");
303
+ // #4020: and the gate resolution that admitted the deploy.
304
+ expect(gateAdmission(second.results[0])).toEqual({ op: "api", gate: "prod-release", resolvedAt: "2026-01-03T00:01:00Z" });
305
+ vi.unstubAllEnvs();
297
306
 
298
307
  // The same approval does not pass a promote of another release (#2574).
299
308
  const other = await runPromotion({
@@ -39,7 +39,7 @@
39
39
  */
40
40
 
41
41
  import { parseWiringRef, type WiringRef } from "../lint/rules/comp/support";
42
- import { latestPerComponent, type ReleaseRecord, type ReleaseRecordInput, type RunOrigin } from "../lifecycle/release-ledger";
42
+ import { latestPerComponent, type ReleaseRecord, type ReleaseRecordInput, type ReleaseAdmission, type RunOrigin } from "../lifecycle/release-ledger";
43
43
  import { CapabilityRegistry, type Capability } from "./capability";
44
44
  import {
45
45
  runInterpretDriver,
@@ -47,12 +47,14 @@ import {
47
47
  type DriverComponent,
48
48
  type DriverComponentResult,
49
49
  type DriverGate,
50
+ type ReleaseGateBinding,
50
51
  type DriverPhase,
51
52
  type DriverRunResult,
52
53
  type DriverStep,
53
54
  } from "./driver";
54
55
  import type { GateLedgerPort } from "../op/gate";
55
56
  import type { RunProgressEvent } from "./run-progress";
57
+ import type { RunEventSink } from "../run-event-stream";
56
58
 
57
59
  /**
58
60
  * Step kinds that produce an artifact at build time: the build family, and the
@@ -535,8 +537,12 @@ export interface RunPromotionOptions {
535
537
  registry: CapabilityRegistry;
536
538
  componentOutputs?: Record<string, Record<string, unknown>>;
537
539
  gates?: GateLedgerPort;
540
+ /** A promote under a release (#4045): what every gate it reaches binds. */
541
+ releaseGate?: ReleaseGateBinding;
538
542
  now?: string;
539
543
  onProgress?: (event: RunProgressEvent) => void;
544
+ /** Where the run's events go (#4041, `--events-json`). */
545
+ onRunEvent?: RunEventSink;
540
546
  }
541
547
 
542
548
  /**
@@ -566,7 +572,9 @@ export async function runPromotion(options: RunPromotionOptions): Promise<Driver
566
572
  return item ? { version: item.digest, revision: item.source.gitSha } : undefined;
567
573
  },
568
574
  ...(options.onProgress ? { onProgress: options.onProgress } : {}),
575
+ ...(options.onRunEvent ? { onRunEvent: options.onRunEvent } : {}),
569
576
  ...(options.gates ? { gates: options.gates } : {}),
577
+ ...(options.releaseGate ? { releaseGate: options.releaseGate } : {}),
570
578
  ...(options.now ? { now: options.now } : {}),
571
579
  });
572
580
  } catch (err) {
@@ -584,6 +592,20 @@ export function gateApprover(result: DriverComponentResult | undefined): string
584
592
  return approver;
585
593
  }
586
594
 
595
+ /**
596
+ * The gate resolution that admitted a component's last gate in this run (#4020):
597
+ * which gate, in the component's gate ledger, and when the resolution was
598
+ * recorded. Undefined for a component that passed no gate.
599
+ */
600
+ export function gateAdmission(result: DriverComponentResult | undefined): ReleaseAdmission | undefined {
601
+ let admission: ReleaseAdmission | undefined;
602
+ for (const record of result?.records ?? []) {
603
+ // #4045: a release gate's resolution is in the release gate's ledger, not the component's.
604
+ if (record.approval) admission = { op: record.approval.op ?? result!.component, gate: record.approval.gate, resolvedAt: record.approval.timestamp };
605
+ }
606
+ return admission;
607
+ }
608
+
587
609
  /** What a promote, rollback or redeploy knows about its own run, for the records it writes. */
588
610
  export interface DeployRunInfo {
589
611
  runId: string;
@@ -591,6 +613,8 @@ export interface DeployRunInfo {
591
613
  actor: string;
592
614
  timestamp: string;
593
615
  approver?: string;
616
+ /** The gate resolution that admitted the deploy (#4020), from {@link gateAdmission}. */
617
+ admission?: ReleaseAdmission;
594
618
  }
595
619
 
596
620
  /** The fields a promote, a rollback and a redeploy record share: the artifact is the earlier release's own. */
@@ -606,6 +630,7 @@ function sharedRecord(item: PromotionItem, env: string, run: DeployRunInfo): Rel
606
630
  timestamp: run.timestamp,
607
631
  actor: run.actor,
608
632
  ...(run.approver ? { approver: run.approver } : {}),
633
+ ...(run.admission ? { admission: run.admission } : {}),
609
634
  ...(source.manifestDigest ? { manifestDigest: source.manifestDigest } : {}),
610
635
  ...(source.inputDigest ? { inputDigest: source.inputDigest } : {}),
611
636
  };
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Compiling one environment of a release, and building its artifacts once
3
+ * (#4026, ws-104 question 5), against fixture projects in temp directories.
4
+ */
5
+
6
+ import { describe, test, expect } from "vitest";
7
+ import { withTestDir } from "@intentius/chant-test-utils";
8
+ import { mkdirSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { CapabilityRegistry, type DeployContext } from "./capability";
11
+ import type { DriverComponent } from "./driver";
12
+ import {
13
+ buildReleaseArtifacts,
14
+ buildStepsOf,
15
+ compileEnvironment,
16
+ ReleaseCompileError,
17
+ templateRefs,
18
+ withOnlyArchiveSteps,
19
+ } from "./release-compile";
20
+
21
+ const api: DriverComponent = {
22
+ name: "api",
23
+ dependsOn: ["db"],
24
+ deploy: [
25
+ { phase: "Build", steps: [{ kind: "docker-build", context: "api", into: "api.tar" }, { kind: "zip-package", source: "fn", into: "fn.zip" }] },
26
+ { phase: "Publish", steps: [{ kind: "publish-image", from: "archive:api.tar", template: "not-a-template.json" }] },
27
+ {
28
+ phase: "Apply",
29
+ steps: [
30
+ { kind: "gate", gate: "prod" },
31
+ { kind: "cfn-deploy", template: "dist/api.template.json", imageRef: "@Publish.digest" },
32
+ { kind: "cfn-deploy", template: "archive:dist/edge.template.json" },
33
+ { kind: "helm-upgrade", chart: "charts/api", values: ["values/base.yaml", "$env.VALUES"] },
34
+ { kind: "kubectl-apply", manifest: "@Render.path" },
35
+ { phase: "Nested", steps: [{ kind: "kubectl-apply", manifest: "k8s/api.yaml" }] },
36
+ ],
37
+ },
38
+ ],
39
+ };
40
+
41
+ describe("templateRefs", () => {
42
+ test("lists the literal paths in template, manifest and values of deploy steps, without build or publish steps", () => {
43
+ expect(templateRefs(api).map((r) => [r.kind, r.field, r.path])).toEqual([
44
+ ["cfn-deploy", "template", "dist/api.template.json"],
45
+ ["cfn-deploy", "template", "dist/edge.template.json"],
46
+ ["helm-upgrade", "values", "values/base.yaml"],
47
+ ["kubectl-apply", "manifest", "k8s/api.yaml"],
48
+ ]);
49
+ });
50
+ });
51
+
52
+ describe("withOnlyArchiveSteps", () => {
53
+ test("keeps the archive steps alone, drops gates and every other step, and the dependencies", () => {
54
+ expect(withOnlyArchiveSteps(api)).toEqual({
55
+ name: "api",
56
+ dependsOn: [],
57
+ deploy: [{ phase: "Build", steps: [{ kind: "docker-build", context: "api", into: "api.tar" }, { kind: "zip-package", source: "fn", into: "fn.zip" }] }],
58
+ });
59
+ expect(withOnlyArchiveSteps({ name: "x", deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy", template: "t.json" }] }] })).toBeUndefined();
60
+ });
61
+
62
+ test("buildStepsOf lists the build-time steps in order", () => {
63
+ expect(buildStepsOf(api).map((s) => s.kind)).toEqual(["docker-build", "zip-package"]);
64
+ });
65
+ });
66
+
67
+ function project(dir: string): void {
68
+ writeFileSync(
69
+ join(dir, "api.component.ts"),
70
+ `export const api = {
71
+ name: "api",
72
+ dependsOn: [],
73
+ deploy: [
74
+ { phase: "Build", steps: [{ kind: "docker-build", context: "svc", into: "api.tar" }] },
75
+ { phase: "Publish", steps: [{ kind: "publish-image", from: "archive:api.tar" }] },
76
+ { phase: "Apply", steps: [{ kind: "gate", gate: "go" }, { kind: "cfn-deploy", template: "dist/api.template.json", imageRef: "@Publish.digest" }] },
77
+ ],
78
+ };
79
+ export const off = { name: "off", dependsOn: [], enabled: false, deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy", template: "dist/missing.json" }] }] };
80
+ `,
81
+ );
82
+ mkdirSync(join(dir, "svc"), { recursive: true });
83
+ writeFileSync(join(dir, "svc", "Dockerfile"), "FROM scratch\n");
84
+ }
85
+
86
+ describe("compileEnvironment", () => {
87
+ test("runs the build command with CHANT_ENV, then reads the templates the enabled components name", async () => {
88
+ await withTestDir(async (dir) => {
89
+ project(dir);
90
+ const target = await compileEnvironment(
91
+ { projectDir: dir, buildCommand: `mkdir -p dist && printf '{"env":"%s"}' "$CHANT_ENV" > dist/api.template.json` },
92
+ "staging",
93
+ );
94
+ expect(target.env).toBe("staging");
95
+ expect(target.components).toEqual(["api"]);
96
+ expect(target.templates).toEqual([{ component: "api", path: "dist/api.template.json", content: `{"env":"staging"}` }]);
97
+ expect(target.buildSteps).toEqual({ api: [{ kind: "docker-build", context: "svc", into: "api.tar" }] });
98
+ expect(target.buildInputs).toEqual({ api: ["svc", "svc/Dockerfile"] });
99
+ expect(target.componentFiles).toEqual({ api: "api.component.ts" });
100
+ });
101
+ });
102
+
103
+ test("refuses when a template a deploy names does not exist after the build", async () => {
104
+ await withTestDir(async (dir) => {
105
+ project(dir);
106
+ const err = await compileEnvironment({ projectDir: dir }, "dev").catch((e: unknown) => e);
107
+ expect(err).toBeInstanceOf(ReleaseCompileError);
108
+ expect((err as Error).message).toMatch(/reads template "dist\/api.template.json", and no such file exists after the build for "dev"/);
109
+ });
110
+ });
111
+
112
+ test("refuses when the build command fails", async () => {
113
+ await withTestDir(async (dir) => {
114
+ project(dir);
115
+ await expect(compileEnvironment({ projectDir: dir, buildCommand: "exit 3" }, "dev")).rejects.toThrow(/the build command for "dev" exited 3/);
116
+ });
117
+ });
118
+ });
119
+
120
+ describe("buildReleaseArtifacts", () => {
121
+ test("runs only the archive steps, once, and names each image and asset with its digest", async () => {
122
+ await withTestDir(async (dir) => {
123
+ writeFileSync(
124
+ join(dir, "api.component.ts"),
125
+ `export const api = {
126
+ name: "api",
127
+ dependsOn: [],
128
+ deploy: [
129
+ { phase: "Build", steps: [{ kind: "docker-build", context: ".", into: "api.tar" }, { kind: "zip-package", source: "fn", into: "fn.zip" }] },
130
+ { phase: "Apply", steps: [{ kind: "gate", gate: "go" }, { kind: "cfn-deploy", template: "t.json" }] },
131
+ ],
132
+ };`,
133
+ );
134
+ const ran: string[] = [];
135
+ const registry = new CapabilityRegistry();
136
+ registry.register({ kind: "docker-build", async run(ctx: DeployContext, input: { into: string }) { ran.push(`${ctx.env}:docker-build`); return { archivePath: input.into, digest: "sha256:img" }; } } as never);
137
+ registry.register({ kind: "zip-package", async run(ctx: DeployContext, input: { into: string }) { ran.push(`${ctx.env}:zip-package`); return { archivePath: input.into, digest: "sha256:zip" }; } } as never);
138
+ registry.register({ kind: "cfn-deploy", async run() { ran.push("cfn-deploy"); return {}; } } as never);
139
+ const artifacts = await buildReleaseArtifacts({ projectDir: dir }, "dev", ["api"], registry);
140
+ expect(ran).toEqual(["dev:docker-build", "dev:zip-package"]);
141
+ expect(artifacts).toEqual([
142
+ { component: "api", kind: "image", path: "api.tar", digest: "sha256:img" },
143
+ { component: "api", kind: "asset", path: "fn.zip", digest: "sha256:zip" },
144
+ ]);
145
+ });
146
+ });
147
+ });