@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
@@ -12,25 +12,36 @@
12
12
  * failed or stopped at a gate, so the ledger never misses a deploy that
13
13
  * happened. Unlike an auto-recorded release, the record is what these
14
14
  * commands are for, so a failed write is an error rather than a warning.
15
+ * A run that applied partly, or failed, also appends a deploy outcome
16
+ * (#4035, ../../lifecycle/deploy-outcome.ts) naming what it did to each
17
+ * component, so `workspace status` reads the environment as mixed.
18
+ *
19
+ * `rollback --release` (#4035) is a promote of an earlier release into the
20
+ * environment: every component of the release at the digest the environment
21
+ * ran from it, through the release gate `promote --release` binds (#4045).
15
22
  */
16
23
  import { resolve } from "node:path";
17
24
  import { readFileSync } from "node:fs";
18
25
  import { loadChantConfig } from "../../config.js";
26
+ import { promotionOrder } from "../../workspace/promotion-order.js";
27
+ import { resolvePromotionSource } from "../../components/promote-order.js";
19
28
  import { fetchLifecycle, getHeadCommit, pushLifecycle } from "../../lifecycle/git.js";
20
- import { appendReleaseRecord, readReleaseLedger, resolveRunId, } from "../../lifecycle/release-ledger.js";
29
+ import { appendReleaseRecord, latestPerComponent, readReleaseLedger, resolveRunId, } from "../../lifecycle/release-ledger.js";
21
30
  import { isComponentList, resolveComponentTargets } from "../../components/cli-support.js";
22
31
  import { applyConfigDefaults } from "../../components/config-defaults.js";
23
32
  import { fanOutRegistry } from "../../components/fan-out-support.js";
24
33
  import { renderDriverHuman } from "../../components/driver-output.js";
25
34
  import { ndjsonProgressSink } from "../../components/run-progress.js";
26
- import { parseDigestPins, planPromotion, planRollback, planRedeploy, withoutBuildSteps, runPromotion, promotionRecord, rollbackRecord, redeployRecord, gateApprover, } from "../../components/promote.js";
35
+ import { openCliRunEvents, refusesBothStreams } from "../run-events.js";
36
+ import { parseDigestPins, planPromotion, planRollback, planReleaseRollback, planRedeploy, withoutBuildSteps, runPromotion, promotionRecord, rollbackRecord, redeployRecord, gateApprover, gateAdmission, } from "../../components/promote.js";
27
37
  import { summaryLedgerPrefix, writeGatedRunSummary } from "../../op/gate-summary.js";
28
- import { approveCommand, describeGateMismatch, gateIsSealed } from "../../op/gate.js";
38
+ import { approveCommand, describeGateMismatch, gateIsSealed, gitGateLedgerPort } from "../../op/gate.js";
29
39
  import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli.js";
30
40
  import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format.js";
31
41
  import { GATED_EXIT_CODE } from "./run.js";
32
- const PROMOTE_USAGE = "chant components promote --from <env> --to <env> [--component <name> [--digest <sha256:...>] | --digest <component>=<sha256:...> ...] [--dry-run] [--json]";
33
- const ROLLBACK_USAGE = "chant components rollback <env> --component <name> [--digest <sha256:...>] [--dry-run] [--json]";
42
+ import { redeployLine, releaseRollbackLine, rollbackTarget, unsettledComponents } from "../../lifecycle/mixed-environment.js";
43
+ const PROMOTE_USAGE = "chant components promote --to <env> [--from <env>] [--allow-skip] [--release <label|digest>] [--component <name> [--digest <sha256:...>] | --digest <component>=<sha256:...> ...] [--dry-run] [--json]";
44
+ const ROLLBACK_USAGE = "chant components rollback <env> (--component <name> [--digest <sha256:...>] | --release <label|digest>) [--dry-run] [--json]";
34
45
  const REDEPLOY_USAGE = "chant components redeploy <env> --component <name> [--digest <sha256:...>] [--dry-run] [--json]";
35
46
  function renderPlan(verb, plan, removed) {
36
47
  console.error(formatBold(verb === "promote" ? `promote ${plan.from} -> ${plan.to}` : `${verb} ${plan.to}`));
@@ -96,13 +107,118 @@ async function readLedger(env) {
96
107
  }
97
108
  return ledger.records;
98
109
  }
110
+ /**
111
+ * Plan, store and bind the release gate for a promote under a release
112
+ * (#4045, ws-104 questions 2 and 6). Undefined when no promoted composition
113
+ * declares a gate, so there is nothing to bind. A number is the exit code of
114
+ * a refusal already printed: a provisional member, or a member that failed
115
+ * to plan. On an empty change set, each gate gets an `empty: true`
116
+ * resolution the run then counts once it reads the stored document back.
117
+ */
118
+ async function bindReleaseGate(input) {
119
+ const { plan, under, port } = input;
120
+ const gate = await import("../../components/release-gate.js");
121
+ const gates = gate.gateNamesOf(input.prepared);
122
+ if (gates.length === 0)
123
+ return undefined;
124
+ const env = plan.to;
125
+ const releaseDigest = under.release.releaseDigest;
126
+ // What the environment runs now, to compare each component with.
127
+ const { readRelease } = await import("../../lifecycle/release-store.js");
128
+ const envRecords = (await readLedger(env)).filter((r) => r.env === env);
129
+ const latest = latestPerComponent(envRecords);
130
+ const releases = new Map();
131
+ const current = new Map();
132
+ for (const item of plan.items) {
133
+ const record = latest.get(item.component);
134
+ if (!record)
135
+ continue;
136
+ const named = record.releaseDigest;
137
+ if (named !== undefined && !releases.has(named))
138
+ releases.set(named, await readRelease(named).catch(() => null));
139
+ current.set(item.component, { record, release: named !== undefined ? (releases.get(named) ?? null) : null });
140
+ }
141
+ // #4035: a component a run left failed or gated runs something the ledger doesn't say.
142
+ const { readDeployOutcomes } = await import("../../lifecycle/deploy-outcome.js");
143
+ const unsettled = new Map();
144
+ for (const [component, u] of unsettledComponents(envRecords, await readDeployOutcomes(env).catch(() => []))) {
145
+ unsettled.set(component, `the ${u.outcome.verb} run ${u.outcome.runId} left ${component} ${u.state} in "${env}" and nothing has been recorded for it since, so what it runs is not known`);
146
+ }
147
+ const doc = await gate.planReleaseChangeSet({
148
+ release: under.release,
149
+ env,
150
+ components: input.prepared,
151
+ digests: new Map(plan.items.map((i) => [i.component, i.digest])),
152
+ registry: input.registry,
153
+ current,
154
+ unsettled,
155
+ seededOutputs: input.seededOutputs,
156
+ });
157
+ const provisional = gate.provisionalMembers(doc);
158
+ if (provisional.length > 0) {
159
+ console.error(formatError({
160
+ message: `the change set of release ${under.label ?? releaseDigest} for "${env}" has provisional members (${provisional.join(", ")}), which no approval covers; nothing was deployed`,
161
+ hint: "Deploy what they read first, so they plan against real values.",
162
+ }));
163
+ return 1;
164
+ }
165
+ const failed = doc.members.filter((m) => m.status === "failed");
166
+ if (failed.length > 0) {
167
+ for (const m of failed)
168
+ console.error(formatError({ message: `${m.member} failed to plan for "${env}"${m.error ? `: ${m.error}` : ""}` }));
169
+ console.error(formatError({ message: "nothing was deployed" }));
170
+ return 1;
171
+ }
172
+ // The document is stored before the gate is decided, so the plan a pending
173
+ // gate waits on can be read, and an empty pass can be checked.
174
+ const { storeGateChangeSet } = await import("../../lifecycle/gate-change-set.js");
175
+ try {
176
+ const stored = await storeGateChangeSet({ doc, cwd: input.projectPath, at: input.head ?? null });
177
+ if (stored.warning)
178
+ console.error(formatWarning({ message: stored.warning }));
179
+ }
180
+ catch (err) {
181
+ console.error(formatWarning({ message: `could not store the change set ${doc.digest}: ${err instanceof Error ? err.message : String(err)}` }));
182
+ }
183
+ const { isEmptyChangeSet } = await import("../../change-set.js");
184
+ const empty = isEmptyChangeSet(doc);
185
+ console.error(formatInfo(`release gate: change set ${doc.digest} for "${env}"` +
186
+ (empty ? ", empty: its gates pass without an approval" : `, ${doc.summary.entries} entr${doc.summary.entries === 1 ? "y" : "ies"}, ${doc.summary.holes} hole(s)`) +
187
+ ` (chant change-set show ${doc.digest})`));
188
+ if (empty) {
189
+ if (!port.appendResolution)
190
+ throw new Error("the gate ledger can't record a resolution, so an empty change set can't pass its gates");
191
+ const { resolutions } = await port.read(gate.RELEASE_GATE_OP);
192
+ const timestamp = new Date().toISOString();
193
+ for (const name of gates) {
194
+ const standing = resolutions.some((r) => r.empty === true && r.gate === name && r.environment === env && r.releaseDigest === releaseDigest && r.planDigest === doc.digest);
195
+ if (standing)
196
+ continue;
197
+ // Pushed with the resolution; the deploy records the promote writes push the same branch.
198
+ await port.appendResolution(gate.emptyResolution({ gate: name, doc, actor: input.actor, timestamp, ...(input.head ? { gitSha: input.head } : {}) }));
199
+ }
200
+ }
201
+ const { readStoredChangeSet } = await import("../../lifecycle/change-set-store.js");
202
+ return {
203
+ doc,
204
+ empty,
205
+ binding: {
206
+ op: gate.RELEASE_GATE_OP,
207
+ planDigest: doc.digest,
208
+ releaseDigest,
209
+ verifyEmpty: gate.emptyVerifier((digest) => readStoredChangeSet(digest, { cwd: input.projectPath }), { releaseDigest, environment: env }),
210
+ },
211
+ };
212
+ }
99
213
  /**
100
214
  * Prepare, run and record a plan. Shared by promote, rollback and redeploy,
101
215
  * which differ only in how the plan was chosen, whether the publish step runs,
102
216
  * and which field names the earlier release on the new record.
103
217
  */
104
- async function deployPlan(ctx, verb, plan, project, actor, record) {
218
+ async function deployPlan(ctx, verb, plan, project, actor, record, under) {
105
219
  const { args } = ctx;
220
+ if (refusesBothStreams(args))
221
+ return 1;
106
222
  const byName = new Map(project.targets.map((c) => [c.name, applyConfigDefaults(c, project.config)]));
107
223
  const prepared = [];
108
224
  const removed = new Map();
@@ -124,6 +240,34 @@ async function deployPlan(ctx, verb, plan, project, actor, record) {
124
240
  console.error(formatError({ message: `nothing was deployed` }));
125
241
  return 1;
126
242
  }
243
+ // #4026: under a release, the deploy reads the templates the release
244
+ // recorded for this environment, checked against their digests.
245
+ let pinnedFiles = [];
246
+ if (under) {
247
+ const { pinReleaseTemplates } = await import("../../components/release-promote.js");
248
+ const { readReleaseTemplate } = await import("../../lifecycle/release-store.js");
249
+ const pinned = await pinReleaseTemplates(prepared, under.release, under.target, (digest) => readReleaseTemplate(digest));
250
+ if ("errors" in pinned) {
251
+ for (const message of pinned.errors)
252
+ console.error(formatError({ message }));
253
+ console.error(formatError({ message: `nothing was deployed` }));
254
+ return 1;
255
+ }
256
+ prepared.splice(0, prepared.length, ...pinned.components);
257
+ pinnedFiles = pinned.files;
258
+ for (const item of plan.items) {
259
+ const named = item.source.releaseDigest;
260
+ if (named !== undefined && named !== under.release.releaseDigest) {
261
+ console.error(formatWarning({ message: `${item.component}: the ${plan.from} deploy being promoted came from release ${named}, not ${under.release.releaseDigest}` }));
262
+ }
263
+ }
264
+ // #4045: the artifact deployed against the artifacts the release built.
265
+ const { releaseArtifactMismatches } = await import("../../components/release-promote.js");
266
+ const { readBuildManifest } = await import("../../lifecycle/build-ledger-store.js");
267
+ const mismatches = await releaseArtifactMismatches(under.release, plan.items.map((i) => ({ component: i.component, source: i.source })), (digest) => readBuildManifest(digest).catch(() => null));
268
+ for (const message of mismatches)
269
+ console.error(formatWarning({ message }));
270
+ }
127
271
  // The composition deployed is this checkout's. Say so when it is not the
128
272
  // commit the artifact was built from.
129
273
  const head = await getHeadCommit().catch(() => undefined);
@@ -135,15 +279,29 @@ async function deployPlan(ctx, verb, plan, project, actor, record) {
135
279
  }));
136
280
  }
137
281
  }
282
+ const releaseJson = under
283
+ ? { release: { releaseDigest: under.release.releaseDigest, label: under.label, templates: under.target.templates } }
284
+ : {};
138
285
  if (args.dryRun) {
139
286
  if (args.json)
140
- console.log(JSON.stringify(planJson(plan, removed), null, 2));
141
- else
287
+ console.log(JSON.stringify({ ...planJson(plan, removed), ...releaseJson }, null, 2));
288
+ else {
142
289
  renderPlan(verb, plan, removed);
290
+ if (under)
291
+ console.error(` under release ${under.label ?? under.release.releaseDigest}: ${under.target.templates.length} template(s) from its "${plan.to}" target`);
292
+ }
143
293
  return 0;
144
294
  }
145
295
  if (!args.json)
146
296
  renderPlan(verb, plan, removed);
297
+ // #4045: the pinned templates are written for this run and removed after it.
298
+ const { writePinnedTemplates, removePinnedTemplates } = await import("../../components/release-promote.js");
299
+ const tidy = () => {
300
+ if (pinnedFiles.length > 0)
301
+ removePinnedTemplates(project.projectPath, pinnedFiles);
302
+ };
303
+ if (pinnedFiles.length > 0)
304
+ writePinnedTemplates(project.projectPath, pinnedFiles);
147
305
  const seededOutputs = {};
148
306
  for (const file of args.seedOutputs ?? []) {
149
307
  try {
@@ -151,34 +309,88 @@ async function deployPlan(ctx, verb, plan, project, actor, record) {
151
309
  }
152
310
  catch (err) {
153
311
  console.error(formatError({ message: `--seed-outputs: could not read "${file}": ${err instanceof Error ? err.message : String(err)}` }));
312
+ tidy();
154
313
  return 1;
155
314
  }
156
315
  }
157
316
  const registry = await fanOutRegistry(project.projectPath, project.config);
158
- const run = await runPromotion({
159
- plan,
160
- components: prepared,
161
- registry,
162
- componentOutputs: seededOutputs,
163
- ...(args.progressJson ? { onProgress: ndjsonProgressSink() } : {}),
317
+ const port = gitGateLedgerPort();
318
+ // #4045: under a release, every gate binds the release's change set for this environment.
319
+ let releaseGate;
320
+ let changeSetJson;
321
+ if (under) {
322
+ try {
323
+ const bound = await bindReleaseGate({ plan, prepared, under, registry, port, seededOutputs, actor: actor, projectPath: project.projectPath, head });
324
+ if (typeof bound === "number") {
325
+ tidy();
326
+ return bound;
327
+ }
328
+ if (bound) {
329
+ releaseGate = bound.binding;
330
+ changeSetJson = { changeSet: bound.doc.digest, empty: bound.empty };
331
+ }
332
+ }
333
+ catch (err) {
334
+ tidy();
335
+ throw err;
336
+ }
337
+ }
338
+ // The run id the release records carry, chosen before the run so that
339
+ // `--events-json` (#4041) names the run by it too.
340
+ const { runId, runOrigin } = resolveRunId(args.runId);
341
+ const events = await openCliRunEvents(args, {
342
+ kind: "component",
343
+ name: `${verb} ${plan.items.map((i) => i.component).join(",")} in ${plan.to}`,
344
+ runId,
345
+ cwd: project.projectPath,
164
346
  });
347
+ let run;
348
+ try {
349
+ run = await runPromotion({
350
+ plan,
351
+ components: prepared,
352
+ registry,
353
+ componentOutputs: seededOutputs,
354
+ gates: port,
355
+ ...(releaseGate ? { releaseGate } : {}),
356
+ ...(args.progressJson ? { onProgress: ndjsonProgressSink() } : {}),
357
+ ...(events ? { onRunEvent: events.emit } : {}),
358
+ });
359
+ }
360
+ catch (err) {
361
+ events?.settle("failed", err instanceof Error ? err.message : String(err));
362
+ throw err;
363
+ }
364
+ finally {
365
+ tidy();
366
+ }
367
+ // The driver settles the stream; this only ends one it left open.
368
+ events?.settle(run.status === "fail" ? "failed" : run.status);
165
369
  if (!args.json)
166
370
  renderDriverHuman(run);
167
371
  // Record every component that deployed, whatever happened to the rest: a
168
372
  // component that reached the environment and is missing from its ledger
169
373
  // would be exactly the unrecorded deploy the ledger exists to rule out.
170
374
  const deployed = plan.items.filter((item) => run.results.some((r) => r.component === item.component && r.status === "ok"));
171
- const { runId, runOrigin } = resolveRunId(args.runId);
172
375
  const timestamp = new Date().toISOString();
173
376
  const recorded = [];
174
377
  let recordError;
175
- if (deployed.length > 0) {
378
+ // #4035: a run that applied partly, or failed, records what it did to each
379
+ // component, so the environment reads as mixed until it is settled.
380
+ const { deployOutcomeOf, appendDeployOutcome } = await import("../../lifecycle/deploy-outcome.js");
381
+ const outcome = deployOutcomeOf({ verb, plan, run, releaseDigest: under?.release.releaseDigest ?? null, runId, timestamp, actor: actor });
382
+ if (deployed.length > 0 || outcome) {
176
383
  try {
177
384
  for (const item of deployed) {
178
- const approver = gateApprover(run.results.find((r) => r.component === item.component));
179
- const { record: written } = await appendReleaseRecord(record(item, plan.to, { runId, ...(runOrigin ? { runOrigin } : {}), actor: actor, timestamp, ...(approver ? { approver } : {}) }));
385
+ const result = run.results.find((r) => r.component === item.component);
386
+ const approver = gateApprover(result);
387
+ const admission = gateAdmission(result);
388
+ const input = record(item, plan.to, { runId, ...(runOrigin ? { runOrigin } : {}), actor: actor, timestamp, ...(approver ? { approver } : {}), ...(admission ? { admission } : {}) });
389
+ const { record: written } = await appendReleaseRecord(under ? { ...input, releaseDigest: under.release.releaseDigest } : input);
180
390
  recorded.push(written);
181
391
  }
392
+ if (outcome)
393
+ await appendDeployOutcome(outcome);
182
394
  await pushLifecycle();
183
395
  }
184
396
  catch (err) {
@@ -186,7 +398,8 @@ async function deployPlan(ctx, verb, plan, project, actor, record) {
186
398
  }
187
399
  }
188
400
  if (args.json) {
189
- console.log(JSON.stringify({ ...planJson(plan, removed), run, recorded }, null, 2));
401
+ const withChangeSet = under && changeSetJson ? { release: { ...releaseJson.release, ...changeSetJson } } : releaseJson;
402
+ console.log(JSON.stringify({ ...planJson(plan, removed), ...withChangeSet, run, recorded, ...(outcome ? { outcome } : {}) }, null, 2));
190
403
  }
191
404
  else {
192
405
  for (const r of recorded) {
@@ -197,9 +410,20 @@ async function deployPlan(ctx, verb, plan, project, actor, record) {
197
410
  : `Redeployed ${formatBold(r.component)} in ${plan.to} at ${r.digest}`));
198
411
  }
199
412
  }
413
+ if (outcome && recordError === undefined) {
414
+ const unsettled = outcome.components.filter((c) => c.outcome === "failed" || c.outcome === "gated");
415
+ console.error(formatWarning({
416
+ message: `"${plan.to}" now runs a mix: ${outcome.components.map((c) => `${c.component} ${c.outcome}`).join(", ")}. ` +
417
+ `chant workspace status ${plan.to} --json lists what each component runs and the rollback line`,
418
+ }));
419
+ for (const c of unsettled)
420
+ console.error(formatInfo(`settle ${c.component}: ${redeployLine(plan.to, c.component)}, or run the same ${verb} again`));
421
+ }
200
422
  if (recordError !== undefined) {
201
423
  console.error(formatError({
202
- message: `deployed to "${plan.to}", but the release record was not written: ${recordError}`,
424
+ message: deployed.length > 0
425
+ ? `deployed to "${plan.to}", but the release record was not written: ${recordError}`
426
+ : `the outcome of the ${verb} in "${plan.to}" was not written: ${recordError}`,
203
427
  hint: `Pull the chant/lifecycle branch and check \`chant components status ${plan.to}\` before running this again.`,
204
428
  }));
205
429
  return 1;
@@ -245,12 +469,119 @@ function actorOrRefuse(ctx) {
245
469
  }
246
470
  return { actor };
247
471
  }
472
+ /** The revoked commits, or the exit code of a refusal already printed when the ci tags can't be judged (#4033). */
473
+ async function revokedOrRefuse(what) {
474
+ const { readRevokedCommits } = await import("../../lifecycle/release-revocation.js");
475
+ try {
476
+ return await readRevokedCommits(process.cwd());
477
+ }
478
+ catch (err) {
479
+ console.error(formatError({
480
+ message: `the ci tags can't be judged, so whether ${what} is revoked is unknown: ${err instanceof Error ? err.message : String(err)} [declaration-invalid]`,
481
+ hint: "Fix ci.green in chant.workspace.json at base; chant workspace check names the problem.",
482
+ }));
483
+ return 1;
484
+ }
485
+ }
486
+ /**
487
+ * Resolve `--release` for a promote or a rollback to `env` (#4026, #4035):
488
+ * the release, its label and its target for `env`. Refused, with the exit
489
+ * code returned, when the release can't be found, has no target for `env`,
490
+ * is of a project with no components (#4046), or was cut from a revoked
491
+ * commit (#4033), so a gate pending for it can't pass.
492
+ */
493
+ async function resolveUnderRelease(ref, env, verb, fix) {
494
+ const { resolveRelease } = await import("../../lifecycle/release-store.js");
495
+ const { targetFor, isImplicitRelease } = await import("../../components/release-promote.js");
496
+ const found = await resolveRelease(ref);
497
+ if (!found) {
498
+ console.error(formatError({ message: `no release is labelled or digested "${ref}" in this member`, hint: "List them with: chant release list" }));
499
+ return 1;
500
+ }
501
+ const target = targetFor(found.release, env);
502
+ if ("error" in target) {
503
+ console.error(formatError({ message: target.error }));
504
+ return 1;
505
+ }
506
+ // #4046: a release of a project with no components records build output
507
+ // and no component, so there is no deploy step here to point at it.
508
+ if (isImplicitRelease(found.release)) {
509
+ const name = found.label ?? found.release.releaseDigest;
510
+ console.error(formatError({
511
+ message: `release ${name} is of a project with no components, so ${verb} has no component to deploy`,
512
+ hint: `Write its "${env}" build output with \`chant release export ${name} --env ${env}\`, then run the project's own deploy against those files.`,
513
+ }));
514
+ return 1;
515
+ }
516
+ // #4033 (ws-105): a release cut from a revoked commit is never deployed,
517
+ // so a gate pending for it can't pass. Read from local tags, as the cut reads them.
518
+ const revoked = await revokedOrRefuse(`release ${found.label ?? found.release.releaseDigest}`);
519
+ if (typeof revoked === "number")
520
+ return revoked;
521
+ const { revocationOf, revokedRefusal } = await import("../../lifecycle/release-revocation.js");
522
+ const revocation = revocationOf(found.release, revoked);
523
+ if (revocation) {
524
+ console.error(formatError(revokedRefusal({ releaseDigest: found.release.releaseDigest, label: found.label }, revocation, await fix(found.release))));
525
+ return 1;
526
+ }
527
+ return { release: found.release, label: found.label, target: target.target };
528
+ }
529
+ /**
530
+ * The line that rolls `env` back past `exclude` (#4035): to the newest
531
+ * release in its ledger that is still kept and not revoked, by label, or
532
+ * null when there is none.
533
+ */
534
+ async function rollbackLineFor(env, records, exclude, revoked) {
535
+ const { readRelease, readCuts, labelsByDigest } = await import("../../lifecycle/release-store.js");
536
+ const { revocationOf } = await import("../../lifecycle/release-revocation.js");
537
+ const target = await rollbackTarget({
538
+ records,
539
+ exclude,
540
+ check: async (digest) => {
541
+ const release = await readRelease(digest).catch(() => null);
542
+ return { kept: release !== null, revoked: release !== null && revocationOf(release, revoked) !== null };
543
+ },
544
+ });
545
+ if (!target)
546
+ return null;
547
+ const labels = await readCuts().then((c) => labelsByDigest(c.cuts), () => new Map());
548
+ return releaseRollbackLine(env, target, labels.get(target));
549
+ }
550
+ /**
551
+ * Refuse a rollback or a redeploy that would put back a release cut from a
552
+ * revoked commit (#4033, #4035), naming the release to go back to instead.
553
+ * Returns the exit code of the refusal, or undefined when nothing restored
554
+ * names a revoked release.
555
+ */
556
+ async function refuseRevokedRestore(verb, plan, records) {
557
+ const named = [...new Set(plan.items.map((i) => i.source.releaseDigest).filter((d) => d !== undefined))];
558
+ if (named.length === 0)
559
+ return undefined;
560
+ const revoked = await revokedOrRefuse(`the release this ${verb} restores`);
561
+ if (typeof revoked === "number")
562
+ return revoked;
563
+ if (revoked.size === 0)
564
+ return undefined;
565
+ const { readRelease, readCuts, labelsByDigest } = await import("../../lifecycle/release-store.js");
566
+ const { revocationOf, revokedRefusal } = await import("../../lifecycle/release-revocation.js");
567
+ for (const digest of named) {
568
+ const release = await readRelease(digest).catch(() => null);
569
+ const revocation = release ? revocationOf(release, revoked) : null;
570
+ if (!revocation)
571
+ continue;
572
+ const labels = await readCuts().then((c) => labelsByDigest(c.cuts), () => new Map());
573
+ const back = await rollbackLineFor(plan.to, records, new Set([digest]), revoked);
574
+ console.error(formatError(revokedRefusal({ releaseDigest: digest, label: labels.get(digest) ?? null }, revocation, back ? `Restore a release cut from a green commit instead: ${back}` : `No other release "${plan.to}" ran is kept and green; promote a release cut from a green commit (\`chant ci last-green\` names the newest).`)));
575
+ console.error(formatError({ message: "nothing was deployed" }));
576
+ return 1;
577
+ }
578
+ return undefined;
579
+ }
248
580
  export async function runComponentsPromote(ctx) {
249
581
  const { args } = ctx;
250
- const from = args.migrateFrom;
251
582
  const to = args.migrateTo;
252
- if (!from || !to) {
253
- console.error(formatError({ message: "--from <env> and --to <env> are required", hint: PROMOTE_USAGE }));
583
+ if (!to) {
584
+ console.error(formatError({ message: "--to <env> is required", hint: PROMOTE_USAGE }));
254
585
  return 1;
255
586
  }
256
587
  const who = actorOrRefuse(ctx);
@@ -259,6 +590,24 @@ export async function runComponentsPromote(ctx) {
259
590
  const project = await loadProject(ctx, args.component ?? "all");
260
591
  if (typeof project === "number")
261
592
  return project;
593
+ // The member's `environments` array is its promotion order (#4030). It
594
+ // defaults --from and refuses a skipped step; a member that declares none
595
+ // has no order, so --from is required and nothing is refused.
596
+ const resolved = resolvePromotionSource({
597
+ order: promotionOrder(project.config.environments),
598
+ to,
599
+ ...(args.migrateFrom ? { from: args.migrateFrom } : {}),
600
+ ...(args.allowSkip ? { allowSkip: true } : {}),
601
+ });
602
+ if ("error" in resolved) {
603
+ console.error(formatError({ message: `${resolved.error.message} [${resolved.error.code}]`, hint: resolved.error.fix }));
604
+ return 1;
605
+ }
606
+ const from = resolved.from;
607
+ if (!from) {
608
+ console.error(formatError({ message: "--from <env> is required: this member declares no environments, so there is no promotion order to default it from", hint: PROMOTE_USAGE }));
609
+ return 1;
610
+ }
262
611
  // Without --component, each --digest is <component>=<digest> (#2602): a
263
612
  // generated promote job pins every component to the release its own
264
613
  // pipeline run recorded, rather than whatever is latest in --from.
@@ -277,6 +626,14 @@ export async function runComponentsPromote(ctx) {
277
626
  return 1;
278
627
  }
279
628
  await fetchLifecycle().catch(() => false);
629
+ // #4026: --release names the release this promote deploys, by label or digest.
630
+ let under;
631
+ if (args.release) {
632
+ const found = await resolveUnderRelease(args.release, to, "promote", () => "Promote a release cut from a green commit: `chant ci last-green` names the newest, and `chant release list` marks the revoked releases. Fetch the tags first: git fetch origin --tags");
633
+ if (typeof found === "number")
634
+ return found;
635
+ under = found;
636
+ }
280
637
  const plan = planPromotion({
281
638
  from,
282
639
  to,
@@ -290,25 +647,36 @@ export async function runComponentsPromote(ctx) {
290
647
  console.error(formatError({ message: plan.error, hint: PROMOTE_USAGE }));
291
648
  return 1;
292
649
  }
293
- return deployPlan(ctx, "promote", plan, project, who.actor, promotionRecord);
650
+ return deployPlan(ctx, "promote", plan, project, who.actor, promotionRecord, under);
294
651
  }
295
652
  export async function runComponentsRollback(ctx) {
296
653
  const { args } = ctx;
297
654
  const env = args.extraPositional;
298
- if (!env || !args.component) {
299
- console.error(formatError({ message: "an environment and --component <name> are required", hint: ROLLBACK_USAGE }));
655
+ if (!env || (!args.component && !args.release)) {
656
+ console.error(formatError({ message: "an environment and --component <name> or --release <label|digest> are required", hint: ROLLBACK_USAGE }));
657
+ return 1;
658
+ }
659
+ if (args.component && args.release) {
660
+ console.error(formatError({ message: "--release rolls back every component of a release, so it does not take --component", hint: ROLLBACK_USAGE }));
661
+ return 1;
662
+ }
663
+ if (args.release && args.digest) {
664
+ console.error(formatError({ message: "--digest picks one release of one component, so it needs --component, not --release", hint: ROLLBACK_USAGE }));
300
665
  return 1;
301
666
  }
302
667
  const who = actorOrRefuse(ctx);
303
668
  if (typeof who === "number")
304
669
  return who;
670
+ if (args.release)
671
+ return runReleaseRollback(ctx, env, args.release, who.actor);
305
672
  const project = await loadProject(ctx, args.component);
306
673
  if (typeof project === "number")
307
674
  return project;
308
675
  await fetchLifecycle().catch(() => false);
676
+ const records = await readLedger(env);
309
677
  const plan = planRollback({
310
678
  env,
311
- records: await readLedger(env),
679
+ records,
312
680
  declared: project.targets.map((c) => c.name),
313
681
  component: args.component,
314
682
  ...(args.digest ? { digest: args.digest } : {}),
@@ -317,8 +685,48 @@ export async function runComponentsRollback(ctx) {
317
685
  console.error(formatError({ message: plan.error, hint: ROLLBACK_USAGE }));
318
686
  return 1;
319
687
  }
688
+ const refused = await refuseRevokedRestore("rollback", plan, records);
689
+ if (refused !== undefined)
690
+ return refused;
320
691
  return deployPlan(ctx, "rollback", plan, project, who.actor, rollbackRecord);
321
692
  }
693
+ /**
694
+ * `chant components rollback <env> --release <label|digest>` (#4035): a
695
+ * gated promote of an earlier release into `env`. Every component of the
696
+ * release is restored at the digest `env` recorded running it from that
697
+ * release, its deploy reads the templates the release recorded for `env`,
698
+ * and its gates bind the release's change set planned against what `env`
699
+ * runs now, as `promote --release` binds them (#4045).
700
+ */
701
+ async function runReleaseRollback(ctx, env, ref, actor) {
702
+ const project = await loadProject(ctx, "all");
703
+ if (typeof project === "number")
704
+ return project;
705
+ await fetchLifecycle().catch(() => false);
706
+ const records = await readLedger(env);
707
+ const under = await resolveUnderRelease(ref, env, "rollback", async (release) => {
708
+ const revoked = await revokedOrRefuse("the release before it").then((r) => (typeof r === "number" ? new Map() : r));
709
+ const back = await rollbackLineFor(env, records, new Set([release.releaseDigest]), revoked);
710
+ return back ? `Roll back to a release cut from a green commit: ${back}` : "Roll back to a release cut from a green commit; `chant release list` marks the revoked releases.";
711
+ });
712
+ if (typeof under === "number")
713
+ return under;
714
+ const { readDeployOutcomes } = await import("../../lifecycle/deploy-outcome.js");
715
+ const outcomes = await readDeployOutcomes(env).catch(() => []);
716
+ const plan = planReleaseRollback({
717
+ env,
718
+ records,
719
+ declared: project.targets.map((c) => c.name),
720
+ release: under.release,
721
+ label: under.label,
722
+ unsettled: new Set(unsettledComponents(records, outcomes).keys()),
723
+ });
724
+ if ("error" in plan) {
725
+ console.error(formatError({ message: plan.error, hint: ROLLBACK_USAGE }));
726
+ return 1;
727
+ }
728
+ return deployPlan(ctx, "rollback", plan, project, actor, rollbackRecord, under);
729
+ }
322
730
  export async function runComponentsRedeploy(ctx) {
323
731
  const { args } = ctx;
324
732
  const env = args.extraPositional;
@@ -333,9 +741,10 @@ export async function runComponentsRedeploy(ctx) {
333
741
  if (typeof project === "number")
334
742
  return project;
335
743
  await fetchLifecycle().catch(() => false);
744
+ const records = await readLedger(env);
336
745
  const plan = planRedeploy({
337
746
  env,
338
- records: await readLedger(env),
747
+ records,
339
748
  declared: project.targets.map((c) => c.name),
340
749
  component: args.component,
341
750
  ...(args.digest ? { digest: args.digest } : {}),
@@ -344,5 +753,8 @@ export async function runComponentsRedeploy(ctx) {
344
753
  console.error(formatError({ message: plan.error, hint: REDEPLOY_USAGE }));
345
754
  return 1;
346
755
  }
756
+ const refused = await refuseRevokedRestore("redeploy", plan, records);
757
+ if (refused !== undefined)
758
+ return refused;
347
759
  return deployPlan(ctx, "redeploy", plan, project, who.actor, redeployRecord);
348
760
  }