@intentius/chant 0.124.27 → 0.124.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (681) hide show
  1. package/README.md +1 -1
  2. package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
  3. package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
  4. package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  6. package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
  7. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  8. package/dist/__golden__/wave-loop/op-held-on.json +2 -0
  9. package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
  10. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  11. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  12. package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
  13. package/dist/__golden__/wave-loop/op-per-run.json +7 -0
  14. package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
  15. package/dist/__golden__/wave-loop/op-sealed.json +3 -0
  16. package/dist/__golden__/wave-loop/op-spend.json +3 -0
  17. package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
  18. package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
  19. package/dist/build-params.d.ts +34 -1
  20. package/dist/build-params.d.ts.map +1 -1
  21. package/dist/build-params.js +31 -1
  22. package/dist/change-set-graph.d.ts +63 -0
  23. package/dist/change-set-graph.d.ts.map +1 -0
  24. package/dist/change-set-graph.js +130 -0
  25. package/dist/change-set.d.ts +62 -3
  26. package/dist/change-set.d.ts.map +1 -1
  27. package/dist/change-set.js +36 -4
  28. package/dist/chant-version.d.ts +9 -0
  29. package/dist/chant-version.d.ts.map +1 -0
  30. package/dist/chant-version.js +16 -0
  31. package/dist/cli/build-params-cli.d.ts +7 -1
  32. package/dist/cli/build-params-cli.d.ts.map +1 -1
  33. package/dist/cli/build-params-cli.js +17 -8
  34. package/dist/cli/commands/build.d.ts +8 -0
  35. package/dist/cli/commands/build.d.ts.map +1 -1
  36. package/dist/cli/commands/build.js +15 -11
  37. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  38. package/dist/cli/commands/check-lexicon-docs.js +30 -2
  39. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  40. package/dist/cli/commands/check-lexicon.js +13 -2
  41. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
  42. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  43. package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
  44. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  45. package/dist/cli/commands/init-lexicon.js +17 -8
  46. package/dist/cli/handlers/approve.d.ts.map +1 -1
  47. package/dist/cli/handlers/approve.js +64 -4
  48. package/dist/cli/handlers/build.d.ts.map +1 -1
  49. package/dist/cli/handlers/build.js +43 -4
  50. package/dist/cli/handlers/change-set.d.ts +10 -0
  51. package/dist/cli/handlers/change-set.d.ts.map +1 -1
  52. package/dist/cli/handlers/change-set.js +51 -1
  53. package/dist/cli/handlers/fan-out.d.ts +10 -1
  54. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  55. package/dist/cli/handlers/fan-out.js +54 -6
  56. package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
  57. package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
  58. package/dist/cli/handlers/lifecycle-gc.js +143 -0
  59. package/dist/cli/handlers/pr.d.ts +16 -2
  60. package/dist/cli/handlers/pr.d.ts.map +1 -1
  61. package/dist/cli/handlers/pr.js +269 -37
  62. package/dist/cli/handlers/promote.d.ts +7 -0
  63. package/dist/cli/handlers/promote.d.ts.map +1 -1
  64. package/dist/cli/handlers/promote.js +440 -28
  65. package/dist/cli/handlers/release.d.ts +91 -0
  66. package/dist/cli/handlers/release.d.ts.map +1 -0
  67. package/dist/cli/handlers/release.js +589 -0
  68. package/dist/cli/handlers/run.d.ts +38 -2
  69. package/dist/cli/handlers/run.d.ts.map +1 -1
  70. package/dist/cli/handlers/run.js +234 -16
  71. package/dist/cli/main.d.ts.map +1 -1
  72. package/dist/cli/main.js +143 -17
  73. package/dist/cli/mcp/workspace-tools.js +1 -1
  74. package/dist/cli/parse-args.d.ts.map +1 -1
  75. package/dist/cli/parse-args.js +73 -5
  76. package/dist/cli/registry.d.ts +43 -5
  77. package/dist/cli/registry.d.ts.map +1 -1
  78. package/dist/cli/run-events.d.ts +30 -0
  79. package/dist/cli/run-events.d.ts.map +1 -0
  80. package/dist/cli/run-events.js +49 -0
  81. package/dist/cli/version.d.ts +3 -5
  82. package/dist/cli/version.d.ts.map +1 -1
  83. package/dist/cli/version.js +3 -13
  84. package/dist/codegen/docs-in-site.d.ts +71 -0
  85. package/dist/codegen/docs-in-site.d.ts.map +1 -0
  86. package/dist/codegen/docs-in-site.js +177 -0
  87. package/dist/codegen/docs-types.d.ts +14 -1
  88. package/dist/codegen/docs-types.d.ts.map +1 -1
  89. package/dist/codegen/docs.d.ts +18 -1
  90. package/dist/codegen/docs.d.ts.map +1 -1
  91. package/dist/codegen/docs.js +40 -7
  92. package/dist/codegen/rehype-base-url.mjs +15 -9
  93. package/dist/components/auto-release.d.ts +6 -1
  94. package/dist/components/auto-release.d.ts.map +1 -1
  95. package/dist/components/auto-release.js +3 -0
  96. package/dist/components/capability.d.ts +9 -0
  97. package/dist/components/capability.d.ts.map +1 -1
  98. package/dist/components/cli-support.d.ts +6 -0
  99. package/dist/components/cli-support.d.ts.map +1 -1
  100. package/dist/components/cli-support.js +10 -9
  101. package/dist/components/driver.d.ts +50 -9
  102. package/dist/components/driver.d.ts.map +1 -1
  103. package/dist/components/driver.js +116 -54
  104. package/dist/components/fan-out-inputs.d.ts +69 -0
  105. package/dist/components/fan-out-inputs.d.ts.map +1 -0
  106. package/dist/components/fan-out-inputs.js +169 -0
  107. package/dist/components/fan-out-record.d.ts +13 -0
  108. package/dist/components/fan-out-record.d.ts.map +1 -1
  109. package/dist/components/fan-out-record.js +16 -0
  110. package/dist/components/fan-out-run.d.ts +7 -0
  111. package/dist/components/fan-out-run.d.ts.map +1 -1
  112. package/dist/components/fan-out-run.js +85 -26
  113. package/dist/components/fan-out.d.ts +4 -0
  114. package/dist/components/fan-out.d.ts.map +1 -1
  115. package/dist/components/pr-pipeline.d.ts +25 -0
  116. package/dist/components/pr-pipeline.d.ts.map +1 -1
  117. package/dist/components/pr-pipeline.js +22 -4
  118. package/dist/components/pr-run.d.ts +11 -0
  119. package/dist/components/pr-run.d.ts.map +1 -1
  120. package/dist/components/pr-run.js +20 -1
  121. package/dist/components/promote-order.d.ts +38 -0
  122. package/dist/components/promote-order.d.ts.map +1 -0
  123. package/dist/components/promote-order.js +62 -0
  124. package/dist/components/promote.d.ts +38 -2
  125. package/dist/components/promote.d.ts.map +1 -1
  126. package/dist/components/promote.js +77 -2
  127. package/dist/components/release-compile.d.ts +142 -0
  128. package/dist/components/release-compile.d.ts.map +1 -0
  129. package/dist/components/release-compile.js +382 -0
  130. package/dist/components/release-gate.d.ts +135 -0
  131. package/dist/components/release-gate.d.ts.map +1 -0
  132. package/dist/components/release-gate.js +288 -0
  133. package/dist/components/release-promote.d.ts +96 -0
  134. package/dist/components/release-promote.d.ts.map +1 -0
  135. package/dist/components/release-promote.js +204 -0
  136. package/dist/components/run-progress.d.ts +7 -0
  137. package/dist/components/run-progress.d.ts.map +1 -1
  138. package/dist/components/wave-plan.d.ts +8 -1
  139. package/dist/components/wave-plan.d.ts.map +1 -1
  140. package/dist/components/wave-plan.js +11 -3
  141. package/dist/config.d.ts +45 -0
  142. package/dist/config.d.ts.map +1 -1
  143. package/dist/config.js +3 -0
  144. package/dist/flag-params.d.ts +54 -0
  145. package/dist/flag-params.d.ts.map +1 -0
  146. package/dist/flag-params.js +130 -0
  147. package/dist/index.d.ts +1 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +2 -0
  150. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  151. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  152. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  153. package/dist/lifecycle/change-set-store.d.ts +68 -0
  154. package/dist/lifecycle/change-set-store.d.ts.map +1 -0
  155. package/dist/lifecycle/change-set-store.js +97 -0
  156. package/dist/lifecycle/commit-sha.d.ts +13 -0
  157. package/dist/lifecycle/commit-sha.d.ts.map +1 -0
  158. package/dist/lifecycle/commit-sha.js +21 -0
  159. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  160. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  161. package/dist/lifecycle/delivery-metrics.js +145 -0
  162. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  163. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  164. package/dist/lifecycle/deploy-outcome.js +101 -0
  165. package/dist/lifecycle/gate-change-set.d.ts +78 -0
  166. package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
  167. package/dist/lifecycle/gate-change-set.js +138 -0
  168. package/dist/lifecycle/gate-ledger.d.ts +51 -4
  169. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  170. package/dist/lifecycle/gate-ledger.js +18 -0
  171. package/dist/lifecycle/git.d.ts +30 -1
  172. package/dist/lifecycle/git.d.ts.map +1 -1
  173. package/dist/lifecycle/git.js +31 -5
  174. package/dist/lifecycle/index.d.ts +4 -0
  175. package/dist/lifecycle/index.d.ts.map +1 -1
  176. package/dist/lifecycle/index.js +4 -0
  177. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  178. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  179. package/dist/lifecycle/mixed-environment.js +154 -0
  180. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  181. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  182. package/dist/lifecycle/publish-attachments.js +179 -0
  183. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  184. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  185. package/dist/lifecycle/publish-credentials.js +69 -0
  186. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  187. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  188. package/dist/lifecycle/publish-declaration.js +83 -0
  189. package/dist/lifecycle/publish-forge.d.ts +53 -0
  190. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  191. package/dist/lifecycle/publish-forge.js +298 -0
  192. package/dist/lifecycle/publish-npm.d.ts +61 -0
  193. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  194. package/dist/lifecycle/publish-npm.js +242 -0
  195. package/dist/lifecycle/publish-oci.d.ts +64 -0
  196. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  197. package/dist/lifecycle/publish-oci.js +383 -0
  198. package/dist/lifecycle/publish-record.d.ts +66 -0
  199. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  200. package/dist/lifecycle/publish-record.js +84 -0
  201. package/dist/lifecycle/publish-targets.d.ts +45 -0
  202. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  203. package/dist/lifecycle/publish-targets.js +85 -0
  204. package/dist/lifecycle/release-cut.d.ts +154 -0
  205. package/dist/lifecycle/release-cut.d.ts.map +1 -0
  206. package/dist/lifecycle/release-cut.js +366 -0
  207. package/dist/lifecycle/release-join.d.ts +95 -0
  208. package/dist/lifecycle/release-join.d.ts.map +1 -0
  209. package/dist/lifecycle/release-join.js +139 -0
  210. package/dist/lifecycle/release-ledger.d.ts +54 -0
  211. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  212. package/dist/lifecycle/release-ledger.js +53 -20
  213. package/dist/lifecycle/release-publish.d.ts +312 -0
  214. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  215. package/dist/lifecycle/release-publish.js +335 -0
  216. package/dist/lifecycle/release-revocation.d.ts +80 -0
  217. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  218. package/dist/lifecycle/release-revocation.js +123 -0
  219. package/dist/lifecycle/release-store.d.ts +200 -0
  220. package/dist/lifecycle/release-store.d.ts.map +1 -0
  221. package/dist/lifecycle/release-store.js +320 -0
  222. package/dist/lifecycle/release-verify.d.ts +61 -0
  223. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  224. package/dist/lifecycle/release-verify.js +92 -0
  225. package/dist/lifecycle/release-workstation.d.ts +91 -0
  226. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  227. package/dist/lifecycle/release-workstation.js +180 -0
  228. package/dist/lifecycle/retention-policy.d.ts +45 -0
  229. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  230. package/dist/lifecycle/retention-policy.js +43 -0
  231. package/dist/lifecycle/retention.d.ts +185 -0
  232. package/dist/lifecycle/retention.d.ts.map +1 -0
  233. package/dist/lifecycle/retention.js +400 -0
  234. package/dist/lifecycle/run-ledger.d.ts +4 -0
  235. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  236. package/dist/lifecycle/run-ledger.js +2 -0
  237. package/dist/op/activities/change-coverage.d.ts.map +1 -1
  238. package/dist/op/activities/change-coverage.js +4 -2
  239. package/dist/op/activities/source-release.d.ts.map +1 -1
  240. package/dist/op/activities/source-release.js +7 -3
  241. package/dist/op/activities/source-rollback.d.ts.map +1 -1
  242. package/dist/op/activities/source-rollback.js +7 -3
  243. package/dist/op/gate-name.d.ts +11 -2
  244. package/dist/op/gate-name.d.ts.map +1 -1
  245. package/dist/op/gate-name.js +12 -3
  246. package/dist/op/gate-run-locator.d.ts +22 -0
  247. package/dist/op/gate-run-locator.d.ts.map +1 -1
  248. package/dist/op/gate-run-locator.js +31 -0
  249. package/dist/op/gate.d.ts +27 -3
  250. package/dist/op/gate.d.ts.map +1 -1
  251. package/dist/op/gate.js +57 -3
  252. package/dist/op/local-executor.d.ts +36 -1
  253. package/dist/op/local-executor.d.ts.map +1 -1
  254. package/dist/op/local-executor.js +158 -52
  255. package/dist/op/op-waves-run.d.ts.map +1 -1
  256. package/dist/op/op-waves-run.js +4 -0
  257. package/dist/op/run-live.d.ts +2 -0
  258. package/dist/op/run-live.d.ts.map +1 -1
  259. package/dist/op/run-live.js +1 -1
  260. package/dist/op/runtime.d.ts +46 -0
  261. package/dist/op/runtime.d.ts.map +1 -1
  262. package/dist/op/runtimes/local.d.ts.map +1 -1
  263. package/dist/op/runtimes/local.js +8 -0
  264. package/dist/plan-summary.d.ts.map +1 -1
  265. package/dist/plan-summary.js +8 -2
  266. package/dist/point-decision.d.ts +2 -0
  267. package/dist/point-decision.d.ts.map +1 -1
  268. package/dist/point-decision.js +9 -2
  269. package/dist/pr-loop.d.ts +7 -0
  270. package/dist/pr-loop.d.ts.map +1 -1
  271. package/dist/pr-loop.js +23 -4
  272. package/dist/pr-report-url.d.ts +82 -0
  273. package/dist/pr-report-url.d.ts.map +1 -0
  274. package/dist/pr-report-url.js +174 -0
  275. package/dist/provenance.d.ts +2 -2
  276. package/dist/provenance.d.ts.map +1 -1
  277. package/dist/run-event-store.d.ts +104 -0
  278. package/dist/run-event-store.d.ts.map +1 -0
  279. package/dist/run-event-store.js +251 -0
  280. package/dist/run-event-stream.d.ts +133 -0
  281. package/dist/run-event-stream.d.ts.map +1 -0
  282. package/dist/run-event-stream.js +242 -0
  283. package/dist/run-event.d.ts +285 -0
  284. package/dist/run-event.d.ts.map +1 -0
  285. package/dist/run-event.js +209 -0
  286. package/dist/wave-runtime.mjs +262 -226
  287. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  288. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  289. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  290. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  291. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  292. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  293. package/dist/workspace/change-set-show.d.ts +56 -0
  294. package/dist/workspace/change-set-show.d.ts.map +1 -0
  295. package/dist/workspace/change-set-show.js +54 -0
  296. package/dist/workspace/change-set-show.schema.json +102 -0
  297. package/dist/workspace/change-set.schema.json +32 -1
  298. package/dist/workspace/changes-cli.d.ts.map +1 -1
  299. package/dist/workspace/changes-cli.js +10 -1
  300. package/dist/workspace/changes.d.ts +37 -0
  301. package/dist/workspace/changes.d.ts.map +1 -1
  302. package/dist/workspace/changes.js +49 -2
  303. package/dist/workspace/changes.schema.json +135 -0
  304. package/dist/workspace/check.schema.json +4 -2
  305. package/dist/workspace/checks/records.d.ts +9 -1
  306. package/dist/workspace/checks/records.d.ts.map +1 -1
  307. package/dist/workspace/checks/records.js +35 -1
  308. package/dist/workspace/checks.d.ts +1 -0
  309. package/dist/workspace/checks.d.ts.map +1 -1
  310. package/dist/workspace/checks.js +2 -1
  311. package/dist/workspace/ci-green-cli.d.ts +19 -4
  312. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  313. package/dist/workspace/ci-green-cli.js +75 -12
  314. package/dist/workspace/ci-green-forge.d.ts +20 -3
  315. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  316. package/dist/workspace/ci-green-forge.js +6 -4
  317. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  318. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  319. package/dist/workspace/ci-green-workflow.js +6 -0
  320. package/dist/workspace/ci-green.d.ts +281 -13
  321. package/dist/workspace/ci-green.d.ts.map +1 -1
  322. package/dist/workspace/ci-green.js +456 -38
  323. package/dist/workspace/ci-last-green.schema.json +32 -2
  324. package/dist/workspace/ci-list.schema.json +525 -0
  325. package/dist/workspace/commit-links.d.ts +85 -0
  326. package/dist/workspace/commit-links.d.ts.map +1 -0
  327. package/dist/workspace/commit-links.js +163 -0
  328. package/dist/workspace/declaration.d.ts +35 -0
  329. package/dist/workspace/declaration.d.ts.map +1 -1
  330. package/dist/workspace/declaration.js +27 -1
  331. package/dist/workspace/declaration.schema.json +76 -0
  332. package/dist/workspace/declared-kinds.d.ts +16 -0
  333. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  334. package/dist/workspace/declared-kinds.js +25 -0
  335. package/dist/workspace/delivery-metrics.d.ts +91 -0
  336. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  337. package/dist/workspace/delivery-metrics.js +217 -0
  338. package/dist/workspace/delivery-metrics.schema.json +519 -0
  339. package/dist/workspace/flags-read.d.ts +37 -0
  340. package/dist/workspace/flags-read.d.ts.map +1 -0
  341. package/dist/workspace/flags-read.js +103 -0
  342. package/dist/workspace/flags.d.ts +100 -0
  343. package/dist/workspace/flags.d.ts.map +1 -0
  344. package/dist/workspace/flags.js +98 -0
  345. package/dist/workspace/graph-cli.js +1 -1
  346. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  347. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  348. package/dist/workspace/identity-gate-rule.js +72 -7
  349. package/dist/workspace/identity.d.ts +12 -0
  350. package/dist/workspace/identity.d.ts.map +1 -1
  351. package/dist/workspace/identity.js +13 -1
  352. package/dist/workspace/intent-cli.d.ts +11 -2
  353. package/dist/workspace/intent-cli.d.ts.map +1 -1
  354. package/dist/workspace/intent-cli.js +54 -5
  355. package/dist/workspace/intent-trace.d.ts +219 -0
  356. package/dist/workspace/intent-trace.d.ts.map +1 -0
  357. package/dist/workspace/intent-trace.js +314 -0
  358. package/dist/workspace/intent.d.ts +36 -7
  359. package/dist/workspace/intent.d.ts.map +1 -1
  360. package/dist/workspace/intent.js +192 -22
  361. package/dist/workspace/intent.schema.json +702 -6
  362. package/dist/workspace/ls.d.ts +4 -0
  363. package/dist/workspace/ls.d.ts.map +1 -1
  364. package/dist/workspace/ls.js +2 -0
  365. package/dist/workspace/ls.schema.json +3 -1
  366. package/dist/workspace/member-pipeline.d.ts +6 -0
  367. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  368. package/dist/workspace/member-pipeline.js +6 -0
  369. package/dist/workspace/overlap.d.ts +146 -0
  370. package/dist/workspace/overlap.d.ts.map +1 -0
  371. package/dist/workspace/overlap.js +276 -0
  372. package/dist/workspace/promotion-order.d.ts +41 -0
  373. package/dist/workspace/promotion-order.d.ts.map +1 -0
  374. package/dist/workspace/promotion-order.js +95 -0
  375. package/dist/workspace/reason-codes.d.ts +4 -0
  376. package/dist/workspace/reason-codes.d.ts.map +1 -1
  377. package/dist/workspace/reason-codes.js +7 -1
  378. package/dist/workspace/records.d.ts +15 -2
  379. package/dist/workspace/records.d.ts.map +1 -1
  380. package/dist/workspace/records.js +32 -0
  381. package/dist/workspace/records.schema.json +21 -2
  382. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  383. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  384. package/dist/workspace/releases.d.ts +81 -0
  385. package/dist/workspace/releases.d.ts.map +1 -0
  386. package/dist/workspace/releases.js +103 -0
  387. package/dist/workspace/releases.schema.json +513 -0
  388. package/dist/workspace/run-event.schema.json +295 -0
  389. package/dist/workspace/status-gates.d.ts +34 -1
  390. package/dist/workspace/status-gates.d.ts.map +1 -1
  391. package/dist/workspace/status-gates.js +31 -2
  392. package/dist/workspace/status-stewards.d.ts +4 -0
  393. package/dist/workspace/status-stewards.d.ts.map +1 -1
  394. package/dist/workspace/status-stewards.js +2 -0
  395. package/dist/workspace/status.d.ts +117 -0
  396. package/dist/workspace/status.d.ts.map +1 -1
  397. package/dist/workspace/status.js +218 -4
  398. package/dist/workspace/status.schema.json +268 -2
  399. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  400. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  401. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  402. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  403. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  404. package/dist/workspace/trust/op-run-statement.js +143 -0
  405. package/dist/workspace/trust/rotation.d.ts +18 -2
  406. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  407. package/dist/workspace/trust/rotation.js +65 -16
  408. package/dist/workspace/trust/seal.d.ts +11 -0
  409. package/dist/workspace/trust/seal.d.ts.map +1 -1
  410. package/dist/workspace/trust/seal.js +9 -0
  411. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  412. package/dist/workspace/trust/ssh-commit.js +10 -10
  413. package/dist/workspace/wip.d.ts.map +1 -1
  414. package/dist/workspace/wip.js +11 -3
  415. package/package.json +1 -1
  416. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  417. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  418. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  419. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  420. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  421. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  422. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  423. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  424. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  425. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  426. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  427. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  428. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  429. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  430. package/src/__golden__/wave-loop/op-spend.json +3 -0
  431. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  432. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  433. package/src/build-params.test.ts +46 -0
  434. package/src/build-params.ts +55 -1
  435. package/src/change-set-graph.test.ts +72 -0
  436. package/src/change-set-graph.ts +141 -0
  437. package/src/change-set.ts +87 -6
  438. package/src/chant-version.ts +16 -0
  439. package/src/cli/build-params-cli.ts +22 -9
  440. package/src/cli/commands/build.ts +25 -11
  441. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  442. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  443. package/src/cli/commands/check-lexicon.ts +14 -2
  444. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  445. package/src/cli/commands/init-lexicon.test.ts +25 -0
  446. package/src/cli/commands/init-lexicon.ts +18 -8
  447. package/src/cli/handlers/approve-release.test.ts +84 -0
  448. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  449. package/src/cli/handlers/approve.ts +65 -2
  450. package/src/cli/handlers/build.test.ts +67 -0
  451. package/src/cli/handlers/build.ts +46 -4
  452. package/src/cli/handlers/change-set.ts +49 -1
  453. package/src/cli/handlers/fan-out.test.ts +228 -1
  454. package/src/cli/handlers/fan-out.ts +53 -6
  455. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  456. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  457. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  458. package/src/cli/handlers/operator.test.ts +37 -0
  459. package/src/cli/handlers/pr.test.ts +341 -0
  460. package/src/cli/handlers/pr.ts +292 -47
  461. package/src/cli/handlers/promote.test.ts +669 -7
  462. package/src/cli/handlers/promote.ts +464 -29
  463. package/src/cli/handlers/release-implicit.e2e.test.ts +161 -0
  464. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  465. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  466. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  467. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  468. package/src/cli/handlers/release.e2e.test.ts +199 -0
  469. package/src/cli/handlers/release.ts +593 -0
  470. package/src/cli/handlers/run-auto-release.test.ts +80 -0
  471. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  472. package/src/cli/handlers/run-release.test.ts +55 -0
  473. package/src/cli/handlers/run.test.ts +30 -0
  474. package/src/cli/handlers/run.ts +243 -18
  475. package/src/cli/main.test.ts +8 -0
  476. package/src/cli/main.ts +143 -17
  477. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  478. package/src/cli/mcp/workspace-tools.ts +1 -1
  479. package/src/cli/parse-args.ts +56 -5
  480. package/src/cli/registry.ts +43 -5
  481. package/src/cli/run-events.ts +57 -0
  482. package/src/cli/version.ts +3 -13
  483. package/src/codegen/docs-in-site.test.ts +166 -0
  484. package/src/codegen/docs-in-site.ts +182 -0
  485. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  486. package/src/codegen/docs-types.ts +14 -1
  487. package/src/codegen/docs.ts +60 -7
  488. package/src/codegen/rehype-base-url.d.mts +3 -1
  489. package/src/codegen/rehype-base-url.mjs +15 -9
  490. package/src/codegen/rehype-base-url.test.ts +34 -0
  491. package/src/components/auto-release.test.ts +30 -0
  492. package/src/components/auto-release.ts +9 -1
  493. package/src/components/capability.ts +9 -0
  494. package/src/components/cli-support.test.ts +82 -0
  495. package/src/components/cli-support.ts +16 -9
  496. package/src/components/driver.test.ts +24 -0
  497. package/src/components/driver.ts +165 -56
  498. package/src/components/fan-out-inputs.test.ts +85 -0
  499. package/src/components/fan-out-inputs.ts +192 -0
  500. package/src/components/fan-out-record.test.ts +10 -0
  501. package/src/components/fan-out-record.ts +19 -0
  502. package/src/components/fan-out-run.ts +104 -35
  503. package/src/components/fan-out.ts +4 -0
  504. package/src/components/pr-pipeline.ts +35 -4
  505. package/src/components/pr-run.ts +30 -1
  506. package/src/components/promote-order.test.ts +90 -0
  507. package/src/components/promote-order.ts +75 -0
  508. package/src/components/promote.test.ts +10 -1
  509. package/src/components/promote.ts +94 -3
  510. package/src/components/release-compile.test.ts +147 -0
  511. package/src/components/release-compile.ts +459 -0
  512. package/src/components/release-gate.test.ts +215 -0
  513. package/src/components/release-gate.ts +355 -0
  514. package/src/components/release-promote.test.ts +159 -0
  515. package/src/components/release-promote.ts +232 -0
  516. package/src/components/run-progress.ts +7 -0
  517. package/src/components/wave-plan.ts +16 -3
  518. package/src/config.ts +15 -1
  519. package/src/flag-params.test.ts +144 -0
  520. package/src/flag-params.ts +134 -0
  521. package/src/index.ts +2 -0
  522. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  523. package/src/lifecycle/change-set-store.test.ts +217 -0
  524. package/src/lifecycle/change-set-store.ts +113 -0
  525. package/src/lifecycle/commit-sha.ts +23 -0
  526. package/src/lifecycle/delivery-metrics.ts +263 -0
  527. package/src/lifecycle/deploy-outcome.ts +147 -0
  528. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  529. package/src/lifecycle/gate-change-set.ts +164 -0
  530. package/src/lifecycle/gate-ledger.test.ts +33 -0
  531. package/src/lifecycle/gate-ledger.ts +68 -3
  532. package/src/lifecycle/git.ts +61 -8
  533. package/src/lifecycle/index.ts +4 -0
  534. package/src/lifecycle/mixed-environment.test.ts +97 -0
  535. package/src/lifecycle/mixed-environment.ts +204 -0
  536. package/src/lifecycle/publish-attachments.ts +196 -0
  537. package/src/lifecycle/publish-credentials.ts +84 -0
  538. package/src/lifecycle/publish-declaration.ts +95 -0
  539. package/src/lifecycle/publish-forge.ts +325 -0
  540. package/src/lifecycle/publish-npm.ts +259 -0
  541. package/src/lifecycle/publish-oci.ts +396 -0
  542. package/src/lifecycle/publish-record.ts +113 -0
  543. package/src/lifecycle/publish-resume.test.ts +168 -0
  544. package/src/lifecycle/publish-targets.test.ts +494 -0
  545. package/src/lifecycle/publish-targets.ts +100 -0
  546. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  547. package/src/lifecycle/release-cut.test.ts +483 -0
  548. package/src/lifecycle/release-cut.ts +503 -0
  549. package/src/lifecycle/release-flag.test.ts +112 -0
  550. package/src/lifecycle/release-join.test.ts +68 -0
  551. package/src/lifecycle/release-join.ts +182 -0
  552. package/src/lifecycle/release-ledger.test.ts +14 -0
  553. package/src/lifecycle/release-ledger.ts +85 -17
  554. package/src/lifecycle/release-publish.test.ts +225 -0
  555. package/src/lifecycle/release-publish.ts +568 -0
  556. package/src/lifecycle/release-revocation.ts +147 -0
  557. package/src/lifecycle/release-store.ts +434 -0
  558. package/src/lifecycle/release-verify.test.ts +110 -0
  559. package/src/lifecycle/release-verify.ts +120 -0
  560. package/src/lifecycle/release-workstation.test.ts +204 -0
  561. package/src/lifecycle/release-workstation.ts +216 -0
  562. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  563. package/src/lifecycle/retention-policy.ts +63 -0
  564. package/src/lifecycle/retention.test.ts +363 -0
  565. package/src/lifecycle/retention.ts +508 -0
  566. package/src/lifecycle/run-ledger.test.ts +10 -0
  567. package/src/lifecycle/run-ledger.ts +6 -0
  568. package/src/op/activities/change-coverage.ts +4 -1
  569. package/src/op/activities/source-release.ts +8 -3
  570. package/src/op/activities/source-rollback.ts +8 -4
  571. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  572. package/src/op/gate-name.ts +13 -3
  573. package/src/op/gate-release.test.ts +126 -0
  574. package/src/op/gate-run-locator.test.ts +110 -0
  575. package/src/op/gate-run-locator.ts +35 -0
  576. package/src/op/gate.ts +88 -6
  577. package/src/op/local-executor.ts +207 -52
  578. package/src/op/op-waves-run.ts +4 -0
  579. package/src/op/run-events.test.ts +166 -0
  580. package/src/op/run-live.ts +1 -1
  581. package/src/op/runtime.ts +39 -0
  582. package/src/op/runtimes/local.test.ts +16 -0
  583. package/src/op/runtimes/local.ts +9 -0
  584. package/src/plan-summary.test.ts +20 -0
  585. package/src/plan-summary.ts +8 -1
  586. package/src/point-decision.ts +12 -2
  587. package/src/pr-loop.ts +25 -4
  588. package/src/pr-report-url.test.ts +131 -0
  589. package/src/pr-report-url.ts +209 -0
  590. package/src/provenance.ts +2 -2
  591. package/src/run-event-store.test.ts +144 -0
  592. package/src/run-event-store.ts +273 -0
  593. package/src/run-event-stream.test.ts +175 -0
  594. package/src/run-event-stream.ts +309 -0
  595. package/src/run-event.test.ts +277 -0
  596. package/src/run-event.ts +471 -0
  597. package/src/wave-loop-golden.test.ts +9 -0
  598. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  599. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  600. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  601. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  602. package/src/workspace/ancestry-index.test.ts +93 -0
  603. package/src/workspace/change-set-show.schema.json +102 -0
  604. package/src/workspace/change-set-show.ts +72 -0
  605. package/src/workspace/change-set.schema.json +32 -1
  606. package/src/workspace/changes-cli.ts +10 -1
  607. package/src/workspace/changes.schema.json +135 -0
  608. package/src/workspace/changes.test.ts +1 -1
  609. package/src/workspace/changes.ts +84 -2
  610. package/src/workspace/check.schema.json +4 -2
  611. package/src/workspace/checks/records.ts +35 -1
  612. package/src/workspace/checks.ts +2 -1
  613. package/src/workspace/ci-green-cli.ts +75 -13
  614. package/src/workspace/ci-green-forge.ts +24 -4
  615. package/src/workspace/ci-green-runs.test.ts +242 -0
  616. package/src/workspace/ci-green-signed.test.ts +302 -0
  617. package/src/workspace/ci-green-workflow.test.ts +16 -0
  618. package/src/workspace/ci-green-workflow.ts +12 -0
  619. package/src/workspace/ci-green.test.ts +131 -2
  620. package/src/workspace/ci-green.ts +635 -42
  621. package/src/workspace/ci-last-green.schema.json +32 -2
  622. package/src/workspace/ci-list.schema.json +525 -0
  623. package/src/workspace/commit-links.test.ts +279 -0
  624. package/src/workspace/commit-links.ts +183 -0
  625. package/src/workspace/declaration.schema.json +76 -0
  626. package/src/workspace/declaration.test.ts +28 -0
  627. package/src/workspace/declaration.ts +60 -2
  628. package/src/workspace/declared-kinds.ts +40 -1
  629. package/src/workspace/delivery-metrics.schema.json +519 -0
  630. package/src/workspace/delivery-metrics.test.ts +236 -0
  631. package/src/workspace/delivery-metrics.ts +286 -0
  632. package/src/workspace/flags-read.ts +121 -0
  633. package/src/workspace/flags.test.ts +141 -0
  634. package/src/workspace/flags.ts +157 -0
  635. package/src/workspace/graph-cli.ts +1 -1
  636. package/src/workspace/identity-gate-rule.ts +91 -7
  637. package/src/workspace/identity.ts +14 -1
  638. package/src/workspace/intent-cli.ts +46 -5
  639. package/src/workspace/intent-trace.test.ts +173 -0
  640. package/src/workspace/intent-trace.ts +484 -0
  641. package/src/workspace/intent.schema.json +702 -6
  642. package/src/workspace/intent.ts +196 -26
  643. package/src/workspace/ls.schema.json +3 -1
  644. package/src/workspace/ls.ts +6 -0
  645. package/src/workspace/member-pipeline.test.ts +17 -0
  646. package/src/workspace/member-pipeline.ts +9 -0
  647. package/src/workspace/older-records.test.ts +412 -0
  648. package/src/workspace/overlap.test.ts +165 -0
  649. package/src/workspace/overlap.ts +340 -0
  650. package/src/workspace/promotion-order.ts +97 -0
  651. package/src/workspace/read-contract.e2e.test.ts +5 -1
  652. package/src/workspace/reason-codes.test.ts +7 -1
  653. package/src/workspace/reason-codes.ts +7 -1
  654. package/src/workspace/record-assets.test.ts +2 -1
  655. package/src/workspace/records-contract.e2e.test.ts +2 -1
  656. package/src/workspace/records.schema.json +21 -2
  657. package/src/workspace/records.ts +36 -2
  658. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  659. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  660. package/src/workspace/releases.schema.json +513 -0
  661. package/src/workspace/releases.test.ts +108 -0
  662. package/src/workspace/releases.ts +133 -0
  663. package/src/workspace/run-event.schema.json +295 -0
  664. package/src/workspace/status-contract.test.ts +81 -1
  665. package/src/workspace/status-gates.ts +64 -1
  666. package/src/workspace/status-mixed.test.ts +152 -0
  667. package/src/workspace/status-revoked.test.ts +153 -0
  668. package/src/workspace/status-stewards.ts +6 -0
  669. package/src/workspace/status.schema.json +268 -2
  670. package/src/workspace/status.ts +334 -4
  671. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  672. package/src/workspace/trust/op-run-statement.ts +148 -0
  673. package/src/workspace/trust/rotation.ts +61 -14
  674. package/src/workspace/trust/seal.ts +10 -0
  675. package/src/workspace/trust/ssh-commit.ts +10 -9
  676. package/src/workspace/wip.ts +11 -2
  677. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  678. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  679. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  680. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  681. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -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,15 @@ 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";
56
+ import type { Release } from "../lifecycle/release-store";
55
57
  import type { RunProgressEvent } from "./run-progress";
58
+ import type { RunEventSink } from "../run-event-stream";
56
59
 
57
60
  /**
58
61
  * Step kinds that produce an artifact at build time: the build family, and the
@@ -247,6 +250,66 @@ export function planRollback(input: {
247
250
  return { from: env, to: env, items: [{ component, digest: target.digest, source: target }], notPromoted: [] };
248
251
  }
249
252
 
253
+ /**
254
+ * Decide what a rollback to a release restores (#4035): every component of
255
+ * `release`, each at the newest record in `env`'s ledger that names the
256
+ * release, so the digest restored is the one the environment ran from it. A
257
+ * component of the release this checkout does not declare is reported and
258
+ * left alone. One the environment never ran from the release has no recorded
259
+ * digest, and is refused. So is a release the environment already runs,
260
+ * unless one of its components is `unsettled` (a run left it failed or gated):
261
+ * then the rollback is what puts that component back too.
262
+ */
263
+ export function planReleaseRollback(input: {
264
+ env: string;
265
+ records: ReleaseRecord[];
266
+ declared: string[];
267
+ release: Pick<Release, "releaseDigest" | "artifactDigests" | "targets">;
268
+ /** The release's label, or null; used in messages. */
269
+ label: string | null;
270
+ /** Components a run left failed or gated in `env` (../lifecycle/mixed-environment.ts). */
271
+ unsettled?: ReadonlySet<string>;
272
+ }): PromotionPlan | { error: string } {
273
+ const { env, records, declared, release } = input;
274
+ const name = input.label ?? release.releaseDigest;
275
+ const target = release.targets.find((t) => t.env === env);
276
+ const inRelease = new Set<string>();
277
+ for (const t of target?.templates ?? []) if (t.component !== null) inRelease.add(t.component);
278
+ for (const a of release.artifactDigests) inRelease.add(a.component);
279
+ const items: PromotionItem[] = [];
280
+ const notPromoted: PromotionPlan["notPromoted"] = [];
281
+ const neverRan: string[] = [];
282
+ for (const component of [...inRelease].sort()) {
283
+ if (!declared.includes(component)) {
284
+ notPromoted.push({ component, reason: `in release ${name} but not declared in this checkout` });
285
+ continue;
286
+ }
287
+ const ran = records.filter((r) => r.component === component && r.releaseDigest === release.releaseDigest);
288
+ if (ran.length === 0) {
289
+ neverRan.push(component);
290
+ continue;
291
+ }
292
+ const record = latestPerComponent(ran).get(component)!;
293
+ items.push({ component, digest: record.digest, source: record });
294
+ }
295
+ if (neverRan.length > 0) {
296
+ return {
297
+ error: `release ${name} never deployed ${neverRan.map((c) => `"${c}"`).join(", ")} to "${env}", so no recorded digest can be restored; ` +
298
+ `promote it instead: chant components promote --to ${env} --release ${name}`,
299
+ };
300
+ }
301
+ if (items.length === 0) return { error: `release ${name} has no component declared in this checkout` };
302
+ const current = latestPerComponent(records);
303
+ const already = items.every((i) => {
304
+ const now = current.get(i.component);
305
+ return now !== undefined && now.releaseDigest === release.releaseDigest && now.digest === i.digest && !input.unsettled?.has(i.component);
306
+ });
307
+ if (already) {
308
+ return { error: `"${env}" already runs release ${name}; nothing to roll back. After a deploy that failed partway, chant components redeploy puts a component's release back` };
309
+ }
310
+ return { from: env, to: env, items, notPromoted };
311
+ }
312
+
250
313
  /**
251
314
  * Decide what a redeploy deploys: the release of `component` the ledger in
252
315
  * `env` records as current. A deploy that fails partway records nothing, so
@@ -535,8 +598,12 @@ export interface RunPromotionOptions {
535
598
  registry: CapabilityRegistry;
536
599
  componentOutputs?: Record<string, Record<string, unknown>>;
537
600
  gates?: GateLedgerPort;
601
+ /** A promote under a release (#4045): what every gate it reaches binds. */
602
+ releaseGate?: ReleaseGateBinding;
538
603
  now?: string;
539
604
  onProgress?: (event: RunProgressEvent) => void;
605
+ /** Where the run's events go (#4041, `--events-json`). */
606
+ onRunEvent?: RunEventSink;
540
607
  }
541
608
 
542
609
  /**
@@ -566,7 +633,9 @@ export async function runPromotion(options: RunPromotionOptions): Promise<Driver
566
633
  return item ? { version: item.digest, revision: item.source.gitSha } : undefined;
567
634
  },
568
635
  ...(options.onProgress ? { onProgress: options.onProgress } : {}),
636
+ ...(options.onRunEvent ? { onRunEvent: options.onRunEvent } : {}),
569
637
  ...(options.gates ? { gates: options.gates } : {}),
638
+ ...(options.releaseGate ? { releaseGate: options.releaseGate } : {}),
570
639
  ...(options.now ? { now: options.now } : {}),
571
640
  });
572
641
  } catch (err) {
@@ -584,6 +653,20 @@ export function gateApprover(result: DriverComponentResult | undefined): string
584
653
  return approver;
585
654
  }
586
655
 
656
+ /**
657
+ * The gate resolution that admitted a component's last gate in this run (#4020):
658
+ * which gate, in the component's gate ledger, and when the resolution was
659
+ * recorded. Undefined for a component that passed no gate.
660
+ */
661
+ export function gateAdmission(result: DriverComponentResult | undefined): ReleaseAdmission | undefined {
662
+ let admission: ReleaseAdmission | undefined;
663
+ for (const record of result?.records ?? []) {
664
+ // #4045: a release gate's resolution is in the release gate's ledger, not the component's.
665
+ if (record.approval) admission = { op: record.approval.op ?? result!.component, gate: record.approval.gate, resolvedAt: record.approval.timestamp };
666
+ }
667
+ return admission;
668
+ }
669
+
587
670
  /** What a promote, rollback or redeploy knows about its own run, for the records it writes. */
588
671
  export interface DeployRunInfo {
589
672
  runId: string;
@@ -591,6 +674,8 @@ export interface DeployRunInfo {
591
674
  actor: string;
592
675
  timestamp: string;
593
676
  approver?: string;
677
+ /** The gate resolution that admitted the deploy (#4020), from {@link gateAdmission}. */
678
+ admission?: ReleaseAdmission;
594
679
  }
595
680
 
596
681
  /** The fields a promote, a rollback and a redeploy record share: the artifact is the earlier release's own. */
@@ -606,6 +691,7 @@ function sharedRecord(item: PromotionItem, env: string, run: DeployRunInfo): Rel
606
691
  timestamp: run.timestamp,
607
692
  actor: run.actor,
608
693
  ...(run.approver ? { approver: run.approver } : {}),
694
+ ...(run.admission ? { admission: run.admission } : {}),
609
695
  ...(source.manifestDigest ? { manifestDigest: source.manifestDigest } : {}),
610
696
  ...(source.inputDigest ? { inputDigest: source.inputDigest } : {}),
611
697
  };
@@ -616,6 +702,11 @@ function refTo(record: ReleaseRecord) {
616
702
  return { env: record.env, runId: record.runId, timestamp: record.timestamp };
617
703
  }
618
704
 
705
+ /** A rollback and a redeploy put an earlier deploy back, so they name the release it came from (#4043). */
706
+ function releaseOf(source: ReleaseRecord): { releaseDigest?: string } {
707
+ return source.releaseDigest !== undefined ? { releaseDigest: source.releaseDigest } : {};
708
+ }
709
+
619
710
  /**
620
711
  * The release record a successful promotion appends to the target ledger.
621
712
  * The digest, git sha and archive identities are the source release's own,
@@ -632,7 +723,7 @@ export function promotionRecord(item: PromotionItem, to: string, run: DeployRunI
632
723
  * release.
633
724
  */
634
725
  export function rollbackRecord(item: PromotionItem, env: string, run: DeployRunInfo): ReleaseRecordInput {
635
- return { ...sharedRecord(item, env, run), restores: refTo(item.source) };
726
+ return { ...sharedRecord(item, env, run), restores: refTo(item.source), ...releaseOf(item.source) };
636
727
  }
637
728
 
638
729
  /**
@@ -642,5 +733,5 @@ export function rollbackRecord(item: PromotionItem, env: string, run: DeployRunI
642
733
  * change, and the record says when it was last put back.
643
734
  */
644
735
  export function redeployRecord(item: PromotionItem, env: string, run: DeployRunInfo): ReleaseRecordInput {
645
- return { ...sharedRecord(item, env, run), redeploys: refTo(item.source) };
736
+ return { ...sharedRecord(item, env, run), redeploys: refTo(item.source), ...releaseOf(item.source) };
646
737
  }
@@ -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
+ });