@intentius/chant 0.124.28 → 0.124.30

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 (449) hide show
  1. package/dist/build-params.d.ts +34 -1
  2. package/dist/build-params.d.ts.map +1 -1
  3. package/dist/build-params.js +31 -1
  4. package/dist/change-set-graph.d.ts +8 -1
  5. package/dist/change-set-graph.d.ts.map +1 -1
  6. package/dist/change-set-graph.js +4 -2
  7. package/dist/change-set.d.ts +10 -0
  8. package/dist/change-set.d.ts.map +1 -1
  9. package/dist/change-set.js +4 -3
  10. package/dist/cli/build-params-cli.d.ts +7 -1
  11. package/dist/cli/build-params-cli.d.ts.map +1 -1
  12. package/dist/cli/build-params-cli.js +17 -8
  13. package/dist/cli/commands/build.d.ts.map +1 -1
  14. package/dist/cli/commands/build.js +6 -3
  15. package/dist/cli/handlers/approve.d.ts.map +1 -1
  16. package/dist/cli/handlers/approve.js +45 -0
  17. package/dist/cli/handlers/build.d.ts.map +1 -1
  18. package/dist/cli/handlers/build.js +18 -4
  19. package/dist/cli/handlers/fan-out.d.ts +9 -0
  20. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  21. package/dist/cli/handlers/fan-out.js +67 -8
  22. package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
  23. package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
  24. package/dist/cli/handlers/lifecycle-gc.js +143 -0
  25. package/dist/cli/handlers/pr.d.ts +11 -3
  26. package/dist/cli/handlers/pr.d.ts.map +1 -1
  27. package/dist/cli/handlers/pr.js +134 -26
  28. package/dist/cli/handlers/promote.d.ts +7 -0
  29. package/dist/cli/handlers/promote.d.ts.map +1 -1
  30. package/dist/cli/handlers/promote.js +210 -33
  31. package/dist/cli/handlers/release.d.ts +62 -5
  32. package/dist/cli/handlers/release.d.ts.map +1 -1
  33. package/dist/cli/handlers/release.js +414 -28
  34. package/dist/cli/handlers/run.d.ts +11 -1
  35. package/dist/cli/handlers/run.d.ts.map +1 -1
  36. package/dist/cli/handlers/run.js +67 -6
  37. package/dist/cli/main.d.ts.map +1 -1
  38. package/dist/cli/main.js +91 -10
  39. package/dist/cli/parse-args.d.ts.map +1 -1
  40. package/dist/cli/parse-args.js +73 -2
  41. package/dist/cli/registry.d.ts +32 -4
  42. package/dist/cli/registry.d.ts.map +1 -1
  43. package/dist/cli/run-events.d.ts +3 -2
  44. package/dist/cli/run-events.d.ts.map +1 -1
  45. package/dist/cli/run-events.js +5 -3
  46. package/dist/components/auto-release.d.ts +2 -0
  47. package/dist/components/auto-release.d.ts.map +1 -1
  48. package/dist/components/auto-release.js +1 -0
  49. package/dist/components/capability.d.ts +9 -0
  50. package/dist/components/capability.d.ts.map +1 -1
  51. package/dist/components/driver.d.ts +8 -0
  52. package/dist/components/driver.d.ts.map +1 -1
  53. package/dist/components/driver.js +35 -0
  54. package/dist/components/fan-out-inputs.d.ts +69 -0
  55. package/dist/components/fan-out-inputs.d.ts.map +1 -0
  56. package/dist/components/fan-out-inputs.js +169 -0
  57. package/dist/components/fan-out-record.d.ts +13 -0
  58. package/dist/components/fan-out-record.d.ts.map +1 -1
  59. package/dist/components/fan-out-record.js +16 -0
  60. package/dist/components/fan-out-run.d.ts.map +1 -1
  61. package/dist/components/fan-out-run.js +50 -0
  62. package/dist/components/fan-out.d.ts +4 -0
  63. package/dist/components/fan-out.d.ts.map +1 -1
  64. package/dist/components/pr-pipeline.d.ts +49 -0
  65. package/dist/components/pr-pipeline.d.ts.map +1 -1
  66. package/dist/components/pr-pipeline.js +38 -4
  67. package/dist/components/pr-run.d.ts +20 -2
  68. package/dist/components/pr-run.d.ts.map +1 -1
  69. package/dist/components/pr-run.js +26 -13
  70. package/dist/components/promote.d.ts +23 -0
  71. package/dist/components/promote.d.ts.map +1 -1
  72. package/dist/components/promote.js +60 -2
  73. package/dist/components/release-compile.d.ts +9 -0
  74. package/dist/components/release-compile.d.ts.map +1 -1
  75. package/dist/components/release-compile.js +9 -3
  76. package/dist/components/release-gate.d.ts +11 -1
  77. package/dist/components/release-gate.d.ts.map +1 -1
  78. package/dist/components/release-gate.js +10 -4
  79. package/dist/components/wave-plan.d.ts +16 -0
  80. package/dist/components/wave-plan.d.ts.map +1 -1
  81. package/dist/components/wave-plan.js +36 -2
  82. package/dist/config.d.ts +38 -0
  83. package/dist/config.d.ts.map +1 -1
  84. package/dist/config.js +2 -0
  85. package/dist/flag-params.d.ts +54 -0
  86. package/dist/flag-params.d.ts.map +1 -0
  87. package/dist/flag-params.js +130 -0
  88. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  89. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  90. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  91. package/dist/lifecycle/change-set-store.d.ts +2 -1
  92. package/dist/lifecycle/change-set-store.d.ts.map +1 -1
  93. package/dist/lifecycle/change-set-store.js +2 -1
  94. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  95. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  96. package/dist/lifecycle/delivery-metrics.js +145 -0
  97. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  98. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  99. package/dist/lifecycle/deploy-outcome.js +101 -0
  100. package/dist/lifecycle/gate-change-set.d.ts +10 -1
  101. package/dist/lifecycle/gate-change-set.d.ts.map +1 -1
  102. package/dist/lifecycle/gate-change-set.js +35 -3
  103. package/dist/lifecycle/gate-ledger.d.ts +2 -3
  104. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  105. package/dist/lifecycle/git.d.ts +30 -1
  106. package/dist/lifecycle/git.d.ts.map +1 -1
  107. package/dist/lifecycle/git.js +31 -5
  108. package/dist/lifecycle/index.d.ts +4 -0
  109. package/dist/lifecycle/index.d.ts.map +1 -1
  110. package/dist/lifecycle/index.js +4 -0
  111. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  112. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  113. package/dist/lifecycle/mixed-environment.js +154 -0
  114. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  115. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  116. package/dist/lifecycle/publish-attachments.js +179 -0
  117. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  118. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  119. package/dist/lifecycle/publish-credentials.js +69 -0
  120. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  121. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  122. package/dist/lifecycle/publish-declaration.js +83 -0
  123. package/dist/lifecycle/publish-forge.d.ts +53 -0
  124. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  125. package/dist/lifecycle/publish-forge.js +298 -0
  126. package/dist/lifecycle/publish-npm.d.ts +61 -0
  127. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  128. package/dist/lifecycle/publish-npm.js +242 -0
  129. package/dist/lifecycle/publish-oci.d.ts +64 -0
  130. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  131. package/dist/lifecycle/publish-oci.js +383 -0
  132. package/dist/lifecycle/publish-record.d.ts +66 -0
  133. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  134. package/dist/lifecycle/publish-record.js +84 -0
  135. package/dist/lifecycle/publish-targets.d.ts +45 -0
  136. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  137. package/dist/lifecycle/publish-targets.js +85 -0
  138. package/dist/lifecycle/release-cut.d.ts +48 -1
  139. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  140. package/dist/lifecycle/release-cut.js +116 -23
  141. package/dist/lifecycle/release-join.d.ts +95 -0
  142. package/dist/lifecycle/release-join.d.ts.map +1 -0
  143. package/dist/lifecycle/release-join.js +139 -0
  144. package/dist/lifecycle/release-ledger.d.ts +8 -3
  145. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  146. package/dist/lifecycle/release-ledger.js +2 -0
  147. package/dist/lifecycle/release-publish.d.ts +312 -0
  148. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  149. package/dist/lifecycle/release-publish.js +335 -0
  150. package/dist/lifecycle/release-revocation.d.ts +80 -0
  151. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  152. package/dist/lifecycle/release-revocation.js +123 -0
  153. package/dist/lifecycle/release-store.d.ts +12 -2
  154. package/dist/lifecycle/release-store.d.ts.map +1 -1
  155. package/dist/lifecycle/release-store.js +4 -2
  156. package/dist/lifecycle/release-verify.d.ts +61 -0
  157. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  158. package/dist/lifecycle/release-verify.js +92 -0
  159. package/dist/lifecycle/release-workstation.d.ts +91 -0
  160. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  161. package/dist/lifecycle/release-workstation.js +180 -0
  162. package/dist/lifecycle/retention-policy.d.ts +45 -0
  163. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  164. package/dist/lifecycle/retention-policy.js +43 -0
  165. package/dist/lifecycle/retention.d.ts +185 -0
  166. package/dist/lifecycle/retention.d.ts.map +1 -0
  167. package/dist/lifecycle/retention.js +400 -0
  168. package/dist/lifecycle/run-ledger.d.ts +1 -1
  169. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  170. package/dist/lifecycle/run-ledger.js +9 -0
  171. package/dist/op/activities/activity-contracts.d.ts +1 -0
  172. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  173. package/dist/op/activities/activity-contracts.js +1 -0
  174. package/dist/op/activities/shell.d.ts +6 -0
  175. package/dist/op/activities/shell.d.ts.map +1 -1
  176. package/dist/op/activities/shell.js +1 -1
  177. package/dist/op/local-executor.d.ts +23 -1
  178. package/dist/op/local-executor.d.ts.map +1 -1
  179. package/dist/op/local-executor.js +9 -3
  180. package/dist/op/runtime.d.ts +27 -0
  181. package/dist/op/runtime.d.ts.map +1 -1
  182. package/dist/op/runtime.js +20 -0
  183. package/dist/plan-summary.d.ts.map +1 -1
  184. package/dist/plan-summary.js +8 -2
  185. package/dist/pr-forge.d.ts +23 -0
  186. package/dist/pr-forge.d.ts.map +1 -1
  187. package/dist/pr-forge.js +44 -0
  188. package/dist/pr-loop.d.ts +17 -0
  189. package/dist/pr-loop.d.ts.map +1 -1
  190. package/dist/pr-loop.js +30 -4
  191. package/dist/pr-report-url.d.ts +82 -0
  192. package/dist/pr-report-url.d.ts.map +1 -0
  193. package/dist/pr-report-url.js +174 -0
  194. package/dist/provenance.d.ts +2 -2
  195. package/dist/provenance.d.ts.map +1 -1
  196. package/dist/run-event-store.d.ts +29 -2
  197. package/dist/run-event-store.d.ts.map +1 -1
  198. package/dist/run-event-store.js +50 -3
  199. package/dist/run-event-stream.d.ts +4 -3
  200. package/dist/run-event-stream.d.ts.map +1 -1
  201. package/dist/run-event-stream.js +7 -3
  202. package/dist/run-event.d.ts +34 -1
  203. package/dist/run-event.d.ts.map +1 -1
  204. package/dist/run-event.js +4 -0
  205. package/dist/wave-runtime.mjs +260 -229
  206. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  207. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  208. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  209. package/dist/workspace/change-set.schema.json +5 -1
  210. package/dist/workspace/check.schema.json +4 -2
  211. package/dist/workspace/checks/records.d.ts +9 -1
  212. package/dist/workspace/checks/records.d.ts.map +1 -1
  213. package/dist/workspace/checks/records.js +35 -1
  214. package/dist/workspace/checks.d.ts +1 -0
  215. package/dist/workspace/checks.d.ts.map +1 -1
  216. package/dist/workspace/checks.js +2 -1
  217. package/dist/workspace/ci-green-forge.d.ts +2 -0
  218. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  219. package/dist/workspace/ci-green.d.ts +10 -0
  220. package/dist/workspace/ci-green.d.ts.map +1 -1
  221. package/dist/workspace/ci-green.js +14 -5
  222. package/dist/workspace/ci-run.d.ts +222 -0
  223. package/dist/workspace/ci-run.d.ts.map +1 -0
  224. package/dist/workspace/ci-run.js +464 -0
  225. package/dist/workspace/ci-serve.d.ts +214 -0
  226. package/dist/workspace/ci-serve.d.ts.map +1 -0
  227. package/dist/workspace/ci-serve.js +412 -0
  228. package/dist/workspace/declaration.d.ts +16 -0
  229. package/dist/workspace/declaration.d.ts.map +1 -1
  230. package/dist/workspace/declaration.js +26 -0
  231. package/dist/workspace/declaration.schema.json +64 -0
  232. package/dist/workspace/declared-kinds.d.ts +16 -0
  233. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  234. package/dist/workspace/declared-kinds.js +25 -0
  235. package/dist/workspace/delivery-metrics.d.ts +91 -0
  236. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  237. package/dist/workspace/delivery-metrics.js +217 -0
  238. package/dist/workspace/delivery-metrics.schema.json +519 -0
  239. package/dist/workspace/flags-read.d.ts +37 -0
  240. package/dist/workspace/flags-read.d.ts.map +1 -0
  241. package/dist/workspace/flags-read.js +103 -0
  242. package/dist/workspace/flags.d.ts +100 -0
  243. package/dist/workspace/flags.d.ts.map +1 -0
  244. package/dist/workspace/flags.js +98 -0
  245. package/dist/workspace/graph-cli.js +1 -1
  246. package/dist/workspace/intent-cli.d.ts +11 -2
  247. package/dist/workspace/intent-cli.d.ts.map +1 -1
  248. package/dist/workspace/intent-cli.js +54 -5
  249. package/dist/workspace/intent-trace.d.ts +219 -0
  250. package/dist/workspace/intent-trace.d.ts.map +1 -0
  251. package/dist/workspace/intent-trace.js +314 -0
  252. package/dist/workspace/intent.d.ts +30 -6
  253. package/dist/workspace/intent.d.ts.map +1 -1
  254. package/dist/workspace/intent.js +141 -21
  255. package/dist/workspace/intent.schema.json +702 -6
  256. package/dist/workspace/overlap.d.ts +146 -0
  257. package/dist/workspace/overlap.d.ts.map +1 -0
  258. package/dist/workspace/overlap.js +276 -0
  259. package/dist/workspace/pr-report.schema.json +5 -0
  260. package/dist/workspace/reason-codes.d.ts +2 -0
  261. package/dist/workspace/reason-codes.d.ts.map +1 -1
  262. package/dist/workspace/reason-codes.js +3 -0
  263. package/dist/workspace/records.d.ts +15 -2
  264. package/dist/workspace/records.d.ts.map +1 -1
  265. package/dist/workspace/records.js +32 -0
  266. package/dist/workspace/records.schema.json +21 -2
  267. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  268. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  269. package/dist/workspace/releases.d.ts +9 -0
  270. package/dist/workspace/releases.d.ts.map +1 -1
  271. package/dist/workspace/releases.js +9 -0
  272. package/dist/workspace/releases.schema.json +123 -1
  273. package/dist/workspace/run-event.schema.json +24 -6
  274. package/dist/workspace/status-gates.d.ts +8 -0
  275. package/dist/workspace/status-gates.d.ts.map +1 -1
  276. package/dist/workspace/status-stewards.d.ts +2 -0
  277. package/dist/workspace/status-stewards.d.ts.map +1 -1
  278. package/dist/workspace/status-stewards.js +1 -0
  279. package/dist/workspace/status.d.ts +78 -0
  280. package/dist/workspace/status.d.ts.map +1 -1
  281. package/dist/workspace/status.js +170 -3
  282. package/dist/workspace/status.schema.json +210 -2
  283. package/dist/workspace/wip.d.ts.map +1 -1
  284. package/dist/workspace/wip.js +11 -3
  285. package/package.json +1 -1
  286. package/src/build-params.test.ts +46 -0
  287. package/src/build-params.ts +55 -1
  288. package/src/change-set-graph.ts +10 -2
  289. package/src/change-set.ts +14 -3
  290. package/src/cli/build-params-cli.ts +22 -9
  291. package/src/cli/commands/build.ts +6 -3
  292. package/src/cli/handlers/approve-release.test.ts +5 -1
  293. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  294. package/src/cli/handlers/approve.ts +47 -0
  295. package/src/cli/handlers/build.test.ts +30 -0
  296. package/src/cli/handlers/build.ts +20 -4
  297. package/src/cli/handlers/fan-out.test.ts +262 -1
  298. package/src/cli/handlers/fan-out.ts +72 -8
  299. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  300. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  301. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  302. package/src/cli/handlers/pr.test.ts +148 -1
  303. package/src/cli/handlers/pr.ts +144 -28
  304. package/src/cli/handlers/promote.test.ts +280 -2
  305. package/src/cli/handlers/promote.ts +210 -34
  306. package/src/cli/handlers/release-implicit.e2e.test.ts +12 -4
  307. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  308. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  309. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  310. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  311. package/src/cli/handlers/release.e2e.test.ts +146 -32
  312. package/src/cli/handlers/release.ts +415 -28
  313. package/src/cli/handlers/run-auto-release.test.ts +9 -0
  314. package/src/cli/handlers/run-release.test.ts +55 -0
  315. package/src/cli/handlers/run.ts +69 -5
  316. package/src/cli/main.test.ts +1 -0
  317. package/src/cli/main.ts +91 -10
  318. package/src/cli/parse-args.ts +54 -2
  319. package/src/cli/registry.ts +32 -4
  320. package/src/cli/run-events.ts +5 -2
  321. package/src/components/auto-release.test.ts +9 -0
  322. package/src/components/auto-release.ts +3 -0
  323. package/src/components/capability.ts +9 -0
  324. package/src/components/driver.test.ts +24 -0
  325. package/src/components/driver.ts +36 -0
  326. package/src/components/fan-out-inputs.test.ts +85 -0
  327. package/src/components/fan-out-inputs.ts +192 -0
  328. package/src/components/fan-out-record.test.ts +10 -0
  329. package/src/components/fan-out-record.ts +19 -0
  330. package/src/components/fan-out-run.ts +48 -0
  331. package/src/components/fan-out.ts +4 -0
  332. package/src/components/pr-pipeline.ts +67 -4
  333. package/src/components/pr-run.test.ts +127 -0
  334. package/src/components/pr-run.ts +32 -12
  335. package/src/components/promote.ts +68 -2
  336. package/src/components/release-compile.ts +17 -3
  337. package/src/components/release-gate.ts +18 -4
  338. package/src/components/wave-plan.ts +45 -2
  339. package/src/config.ts +8 -0
  340. package/src/flag-params.test.ts +144 -0
  341. package/src/flag-params.ts +134 -0
  342. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  343. package/src/lifecycle/change-set-store.ts +2 -1
  344. package/src/lifecycle/delivery-metrics.ts +263 -0
  345. package/src/lifecycle/deploy-outcome.ts +147 -0
  346. package/src/lifecycle/gate-change-set.ts +37 -6
  347. package/src/lifecycle/gate-ledger.ts +2 -2
  348. package/src/lifecycle/git.ts +61 -8
  349. package/src/lifecycle/index.ts +4 -0
  350. package/src/lifecycle/mixed-environment.test.ts +97 -0
  351. package/src/lifecycle/mixed-environment.ts +204 -0
  352. package/src/lifecycle/publish-attachments.ts +196 -0
  353. package/src/lifecycle/publish-credentials.ts +84 -0
  354. package/src/lifecycle/publish-declaration.ts +95 -0
  355. package/src/lifecycle/publish-forge.ts +325 -0
  356. package/src/lifecycle/publish-npm.ts +259 -0
  357. package/src/lifecycle/publish-oci.ts +396 -0
  358. package/src/lifecycle/publish-record.ts +113 -0
  359. package/src/lifecycle/publish-resume.test.ts +168 -0
  360. package/src/lifecycle/publish-targets.test.ts +494 -0
  361. package/src/lifecycle/publish-targets.ts +100 -0
  362. package/src/lifecycle/release-cut.test.ts +74 -2
  363. package/src/lifecycle/release-cut.ts +158 -22
  364. package/src/lifecycle/release-flag.test.ts +112 -0
  365. package/src/lifecycle/release-join.test.ts +68 -0
  366. package/src/lifecycle/release-join.ts +182 -0
  367. package/src/lifecycle/release-ledger.ts +9 -3
  368. package/src/lifecycle/release-publish.test.ts +225 -0
  369. package/src/lifecycle/release-publish.ts +568 -0
  370. package/src/lifecycle/release-revocation.ts +147 -0
  371. package/src/lifecycle/release-store.ts +15 -4
  372. package/src/lifecycle/release-verify.test.ts +110 -0
  373. package/src/lifecycle/release-verify.ts +120 -0
  374. package/src/lifecycle/release-workstation.test.ts +204 -0
  375. package/src/lifecycle/release-workstation.ts +216 -0
  376. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  377. package/src/lifecycle/retention-policy.ts +63 -0
  378. package/src/lifecycle/retention.test.ts +363 -0
  379. package/src/lifecycle/retention.ts +508 -0
  380. package/src/lifecycle/run-ledger.ts +7 -1
  381. package/src/op/activities/activity-contracts.ts +1 -0
  382. package/src/op/activities/shell.ts +7 -1
  383. package/src/op/local-executor.ts +27 -4
  384. package/src/op/runtime.ts +42 -0
  385. package/src/plan-summary.test.ts +20 -0
  386. package/src/plan-summary.ts +8 -1
  387. package/src/pr-forge.test.ts +25 -0
  388. package/src/pr-forge.ts +62 -0
  389. package/src/pr-loop.test.ts +13 -1
  390. package/src/pr-loop.ts +41 -4
  391. package/src/pr-report-url.test.ts +131 -0
  392. package/src/pr-report-url.ts +209 -0
  393. package/src/provenance.ts +2 -2
  394. package/src/run-event-store.ts +63 -3
  395. package/src/run-event-stream.ts +8 -4
  396. package/src/run-event.ts +38 -1
  397. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  398. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  399. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  400. package/src/workspace/change-set.schema.json +5 -1
  401. package/src/workspace/check.schema.json +4 -2
  402. package/src/workspace/checks/records.ts +35 -1
  403. package/src/workspace/checks.ts +2 -1
  404. package/src/workspace/ci-green-forge.ts +2 -0
  405. package/src/workspace/ci-green.ts +20 -5
  406. package/src/workspace/ci-run.test.ts +416 -0
  407. package/src/workspace/ci-run.ts +571 -0
  408. package/src/workspace/ci-serve.test.ts +268 -0
  409. package/src/workspace/ci-serve.ts +521 -0
  410. package/src/workspace/declaration.schema.json +64 -0
  411. package/src/workspace/declaration.test.ts +28 -0
  412. package/src/workspace/declaration.ts +38 -0
  413. package/src/workspace/declared-kinds.ts +40 -1
  414. package/src/workspace/delivery-metrics.schema.json +519 -0
  415. package/src/workspace/delivery-metrics.test.ts +236 -0
  416. package/src/workspace/delivery-metrics.ts +286 -0
  417. package/src/workspace/flags-read.ts +121 -0
  418. package/src/workspace/flags.test.ts +141 -0
  419. package/src/workspace/flags.ts +157 -0
  420. package/src/workspace/graph-cli.ts +1 -1
  421. package/src/workspace/intent-cli.ts +46 -5
  422. package/src/workspace/intent-trace.test.ts +173 -0
  423. package/src/workspace/intent-trace.ts +484 -0
  424. package/src/workspace/intent.schema.json +702 -6
  425. package/src/workspace/intent.ts +148 -25
  426. package/src/workspace/older-records.test.ts +412 -0
  427. package/src/workspace/overlap.test.ts +165 -0
  428. package/src/workspace/overlap.ts +340 -0
  429. package/src/workspace/pr-report.schema.json +5 -0
  430. package/src/workspace/read-contract.e2e.test.ts +2 -1
  431. package/src/workspace/reason-codes.test.ts +5 -0
  432. package/src/workspace/reason-codes.ts +3 -0
  433. package/src/workspace/record-assets.test.ts +2 -1
  434. package/src/workspace/records-contract.e2e.test.ts +2 -1
  435. package/src/workspace/records.schema.json +21 -2
  436. package/src/workspace/records.ts +36 -2
  437. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  438. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  439. package/src/workspace/releases.schema.json +123 -1
  440. package/src/workspace/releases.ts +11 -0
  441. package/src/workspace/run-event.schema.json +24 -6
  442. package/src/workspace/status-contract.test.ts +29 -0
  443. package/src/workspace/status-gates.ts +8 -0
  444. package/src/workspace/status-mixed.test.ts +152 -0
  445. package/src/workspace/status-revoked.test.ts +153 -0
  446. package/src/workspace/status-stewards.ts +3 -0
  447. package/src/workspace/status.schema.json +210 -2
  448. package/src/workspace/status.ts +254 -3
  449. package/src/workspace/wip.ts +11 -2
@@ -60,6 +60,8 @@ function world() {
60
60
  planned: [] as Array<{ root: string; vars?: Record<string, unknown> }>,
61
61
  outputsRead: [] as string[],
62
62
  applied: [] as string[],
63
+ /** The vars each root applied with, after wiring resolved. */
64
+ ranWith: {} as Record<string, Record<string, unknown> | undefined>,
63
65
  fromPlan: {} as Record<string, boolean>,
64
66
  failing: new Set<string>(),
65
67
  planFailing: new Set<string>(),
@@ -100,6 +102,7 @@ function world() {
100
102
  },
101
103
  async run(ctx: DeployContext, input: RootInput) {
102
104
  w.fromPlan[input.root] = ctx.plans?.[input.root] !== undefined;
105
+ w.ranWith[input.root] = input.vars;
103
106
  if (w.failing.has(input.root)) throw new Error(`${input.root} failed`);
104
107
  w.applied.push(input.root);
105
108
  if (input.root === "a") w.state.a = { id: `a-${w.versions.a ?? "v1"}` };
@@ -166,6 +169,130 @@ describe("planPrSet", () => {
166
169
  });
167
170
  });
168
171
 
172
+ describe("a dependency outside the selection (#4104)", () => {
173
+ /** Two members: `up`, and `down` reading its `id`. The pull request changes `down` alone. */
174
+ const PAIR: DriverComponent[] = [root("up"), root("down", ["up"], { upstream: { stackOutput: { stack: "up", name: "id" } } })];
175
+
176
+ function pairWorld() {
177
+ const w = world();
178
+ w.state = { up: { id: "up-live", arn: "arn:up" } };
179
+ w.versions.down = "v2";
180
+ return w;
181
+ }
182
+
183
+ test("the dependent's plan resolves the upstream's outputs from its deployed outputs, and the report lists them", async () => {
184
+ const w = pairWorld();
185
+ const plan = planFanOut({ components: PAIR, changed: ["down"] });
186
+ expect(plan.order).toEqual(["down"]);
187
+ expect(plan.seeds).toEqual(["up"]);
188
+
189
+ const set = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod" });
190
+ expect(w.outputsRead).toEqual(["up"]);
191
+ expect(w.planned).toEqual([{ root: "down", vars: { upstream: "up-live" } }]);
192
+ expect(set.outputs).toEqual({ up: { id: "up-live", arn: "arn:up" } });
193
+ expect(set.deployedOutputs).toEqual({ up: ["arn", "id"] });
194
+
195
+ const report = prReport({
196
+ stage: "plan",
197
+ pr: 12,
198
+ env: "prod",
199
+ base: "1".repeat(40),
200
+ head: "2".repeat(40),
201
+ op: "pr-12",
202
+ gate: "pr-apply",
203
+ plan,
204
+ signal: { changed: ["down"], indeterminate: [], unclaimed: [] },
205
+ set,
206
+ approval: { status: "pending" },
207
+ status: "planned",
208
+ });
209
+ expect(report.deployedOutputs).toEqual({ up: ["arn", "id"] });
210
+ expect(report.selection).toMatchObject({ changed: ["down"], dependents: [] });
211
+ });
212
+
213
+ test("the apply's plan reads the same outputs, gets the approved digest, and applies against them", async () => {
214
+ const w = pairWorld();
215
+ const plan = planFanOut({ components: PAIR, changed: ["down"] });
216
+ const approved = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod" });
217
+
218
+ // pr-apply plans the members again before anything applies.
219
+ w.outputsRead.length = 0;
220
+ const set = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod" });
221
+ expect(w.outputsRead).toEqual(["up"]);
222
+ expect(set.doc.digest).toBe(approved.doc.digest);
223
+ const check = await decidePrGate(memoryGateLedgerPort({ resolutions: [approval(approved.doc.digest)] }), {
224
+ op: "pr-12",
225
+ gate: "pr-apply",
226
+ digest: set.doc.digest,
227
+ now: NOW,
228
+ });
229
+ expect(check.satisfied).toBe(true);
230
+
231
+ const { members, status } = await applyPrSet({ components: PAIR, plan, registry: w.registry, env: "prod", set });
232
+ expect(status).toBe("applied");
233
+ expect(w.applied).toEqual(["down"]);
234
+ expect(w.ranWith.down).toEqual({ upstream: "up-live" });
235
+ expect(members.map((m) => [m.member, m.status, m.inputsMoved])).toEqual([["down", "applied", undefined]]);
236
+ });
237
+
238
+ test("an upstream whose deployed output moved after approval moves the apply's digest", async () => {
239
+ const w = pairWorld();
240
+ const plan = planFanOut({ components: PAIR, changed: ["down"] });
241
+ const approved = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod" });
242
+ w.state.up = { id: "up-moved", arn: "arn:up" };
243
+ const set = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod" });
244
+ expect(set.doc.digest).not.toBe(approved.doc.digest);
245
+ });
246
+
247
+ test("--seed-outputs wins: a seeded upstream is not read and not listed", async () => {
248
+ const w = pairWorld();
249
+ const plan = planFanOut({ components: PAIR, changed: ["down"] });
250
+ const set = await planPrSet({ components: PAIR, plan, registry: w.registry, env: "prod", seededOutputs: { up: { id: "up-seeded" } } });
251
+ expect(w.outputsRead).toEqual([]);
252
+ expect(w.planned).toEqual([{ root: "down", vars: { upstream: "up-seeded" } }]);
253
+ expect(set.deployedOutputs).toBeUndefined();
254
+ });
255
+
256
+ test("an upstream whose steps cannot say leaves the reference unresolved", async () => {
257
+ const w = pairWorld();
258
+ const silent: DriverComponent[] = [
259
+ { name: "up", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "no-outputs" }] }] },
260
+ PAIR[1]!,
261
+ ];
262
+ w.registry.register({ kind: "no-outputs", async run() { return {}; } } as never);
263
+ const plan = planFanOut({ components: silent, changed: ["down"] });
264
+ const set = await planPrSet({ components: silent, plan, registry: w.registry, env: "prod" });
265
+ expect(w.outputsRead).toEqual([]);
266
+ expect(set.outputs).toEqual({});
267
+ expect(set.deployedOutputs).toBeUndefined();
268
+ expect(w.planned).toHaveLength(1);
269
+ expect(w.planned[0]!.vars?.upstream).not.toBe("up-live");
270
+ });
271
+
272
+ test("the selection in place of the whole graph is refused, naming the dependency", async () => {
273
+ const w = pairWorld();
274
+ const plan = planFanOut({ components: PAIR, changed: ["down"] });
275
+ await expect(planPrSet({ components: [PAIR[1]!], plan, registry: w.registry, env: "prod" })).rejects.toThrow(/up, a dependency of a planned member/);
276
+ });
277
+
278
+ test("a resume plans against the record's outputs and reads no dependency they cover", async () => {
279
+ const w = world();
280
+ w.versions.a = "v2";
281
+ const plan = planFanOut({ components: ESTATE, changed: ["a"] });
282
+ const { set, first, record } = await firstAttempt(w, plan, ["app"]);
283
+ expect(first.run.completed).toEqual(["a"]);
284
+ expect(set.deployedOutputs).toEqual({ a: ["id"], net: ["cidr"] });
285
+
286
+ w.outputsRead.length = 0;
287
+ w.state.net = { cidr: "10.9.0.0/16" };
288
+ const resumed = await resumePrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", record, completed: ["a"] });
289
+ if (!resumed.ok) throw new Error(resumed.reasons.join("; "));
290
+ expect(w.outputsRead).toEqual([]);
291
+ expect(resumed.set.doc.digest).toBe(set.doc.digest);
292
+ expect(resumed.set.outputs).toBe(record.planOutputs);
293
+ });
294
+ });
295
+
169
296
  describe("the gate", () => {
170
297
  test("pending with no approval, approved for the digest, changed for another one", async () => {
171
298
  const w = world();
@@ -19,6 +19,16 @@
19
19
  * its dependents in one rollout is what gated waves are for (#3049): each
20
20
  * wave is planned after the one before it applied.
21
21
  *
22
+ * A dependency the change did not select is read the same way (#4104), so
23
+ * a change to one cluster plans it against the network it sits on. That is
24
+ * why `components` is every component in the project and not the selection.
25
+ * `--seed-outputs` wins over a read, and a dependency whose steps cannot say
26
+ * leaves its dependents' references unresolved. The apply stage's plan reads
27
+ * the same dependencies, so on the same estate it gets the digest the plan
28
+ * stage showed, and it applies against the outputs its plans read
29
+ * (`set.outputs`). A resume plans against the outputs the approved plans
30
+ * read, from the record (`planOutputs`), and reads nothing those cover.
31
+ *
22
32
  * ## Finishing an apply that failed partway
23
33
  *
24
34
  * {@link resumePrSet} (#3464) is fan-out's resume (#2417, #3049) for a pull
@@ -53,10 +63,10 @@ import type { PrApplyRecord } from "./fan-out-record";
53
63
  import type { CapabilityRegistry } from "./capability";
54
64
  import type { RunProgressEvent } from "./run-progress";
55
65
  import type { RunEventSink } from "../run-event-stream";
56
- import { planWaveComponent, readComponentOutputs } from "./wave-plan";
66
+ import { planWaveComponent, readDeployedOutputs } from "./wave-plan";
57
67
 
58
68
  export interface PrPlanSetOptions {
59
- /** Every component in the project. */
69
+ /** Every component in the project, not the selection: a planned member's dependency outside it is read too (#4104). */
60
70
  components: DriverComponent[];
61
71
  /** The fan-out the change derives (`planFanOut`). Its `order` is what is planned. */
62
72
  plan: FanOutPlan;
@@ -85,6 +95,12 @@ export interface PrPlanSet {
85
95
  plans: Record<string, Record<string, unknown>>;
86
96
  /** The outputs the plans read: the seeds, and each dependency's outputs as they stood. */
87
97
  outputs: Record<string, Record<string, unknown>>;
98
+ /**
99
+ * Per dependency read from the deployed estate (#4104), the names of the
100
+ * outputs the read gave, sorted. The seeds are not here. Absent when
101
+ * nothing was read. The report's `deployedOutputs`.
102
+ */
103
+ deployedOutputs?: Record<string, string[]>;
88
104
  /** Whether any component failed to plan. */
89
105
  failed: boolean;
90
106
  }
@@ -101,12 +117,15 @@ function failedParts(component: DriverComponent, error: string): ChangeSetPart[]
101
117
  /**
102
118
  * Plan every component in `plan.order`, a wave at a time, each against the
103
119
  * outputs its dependencies have now. Never throws for a component that fails
104
- * to plan: it becomes a failed member, and `failed` is set.
120
+ * to plan: it becomes a failed member, and `failed` is set. Throws when a
121
+ * planned member's dependency is not among `components`, which means the
122
+ * caller passed the selection where the whole graph belongs.
105
123
  */
106
124
  export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
107
125
  const byName = new Map(options.components.map((c) => [c.name, c]));
108
126
  const outputs: Record<string, Record<string, unknown>> = { ...(options.seededOutputs ?? {}) };
109
127
  const read = new Set(Object.keys(outputs));
128
+ const deployed: Record<string, string[]> = {};
110
129
  const parts: ChangeSetPart[] = [];
111
130
  const componentOf = new Map<string, string>();
112
131
  const plans: Record<string, Record<string, unknown>> = {};
@@ -117,15 +136,14 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
117
136
  for (const wave of options.plan.waves) {
118
137
  // The dependencies' outputs first, each read once. A dependency that
119
138
  // cannot say leaves its references unresolved, which its dependent's
120
- // plan reports if it needs them.
121
- for (const name of wave) {
122
- for (const dep of byName.get(name)?.dependsOn ?? []) {
123
- if (read.has(dep) || !byName.has(dep)) continue;
124
- read.add(dep);
125
- const found = await readComponentOutputs(byName.get(dep)!, options.registry, deployContext(options, dep), outputs);
126
- if (found) outputs[dep] = found;
127
- }
139
+ // plan reports if it needs them. A dependency outside the selection is
140
+ // read the same way (#4104).
141
+ const deps = [...new Set(wave.flatMap((name) => byName.get(name)?.dependsOn ?? []))].filter((dep) => !read.has(dep));
142
+ for (const dep of deps) {
143
+ read.add(dep);
144
+ if (!byName.has(dep)) throw new Error(`planPrSet: ${dep}, a dependency of a planned member, is not among the components; pass every component in the project`);
128
145
  }
146
+ Object.assign(deployed, await readDeployedOutputs(deps, byName, options.registry, (dep) => deployContext(options, dep), outputs));
129
147
  const planned = await Promise.all(
130
148
  wave.map(async (name) => {
131
149
  const at = { component: name, phase: PR_PLAN_PHASE, step: "plan" };
@@ -170,7 +188,8 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
170
188
  counts: memberCounts(doc, m.member),
171
189
  ...(m.error ? { error: m.error } : {}),
172
190
  }));
173
- return { doc, members, plans, outputs, failed };
191
+ const deployedOutputs = Object.fromEntries(Object.entries(deployed).sort(([a], [b]) => a.localeCompare(b)));
192
+ return { doc, members, plans, outputs, failed, ...(Object.keys(deployedOutputs).length > 0 ? { deployedOutputs } : {}) };
174
193
  }
175
194
 
176
195
  /**
@@ -488,6 +507,7 @@ export function prReport(input: PrReportInput): PrReport {
488
507
  ...(input.refusal ? { refusal: input.refusal } : {}),
489
508
  ...(input.message ? { message: input.message } : {}),
490
509
  ...(input.resumed ? { resumed: { applied: [...input.resumed].sort() } } : {}),
510
+ ...(input.set.deployedOutputs ? { deployedOutputs: input.set.deployedOutputs } : {}),
491
511
  approval: input.approval,
492
512
  selection: {
493
513
  changed: input.plan.order.filter((c) => changed.has(c)),
@@ -53,6 +53,7 @@ import {
53
53
  type DriverStep,
54
54
  } from "./driver";
55
55
  import type { GateLedgerPort } from "../op/gate";
56
+ import type { Release } from "../lifecycle/release-store";
56
57
  import type { RunProgressEvent } from "./run-progress";
57
58
  import type { RunEventSink } from "../run-event-stream";
58
59
 
@@ -249,6 +250,66 @@ export function planRollback(input: {
249
250
  return { from: env, to: env, items: [{ component, digest: target.digest, source: target }], notPromoted: [] };
250
251
  }
251
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
+
252
313
  /**
253
314
  * Decide what a redeploy deploys: the release of `component` the ledger in
254
315
  * `env` records as current. A deploy that fails partway records nothing, so
@@ -641,6 +702,11 @@ function refTo(record: ReleaseRecord) {
641
702
  return { env: record.env, runId: record.runId, timestamp: record.timestamp };
642
703
  }
643
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
+
644
710
  /**
645
711
  * The release record a successful promotion appends to the target ledger.
646
712
  * The digest, git sha and archive identities are the source release's own,
@@ -657,7 +723,7 @@ export function promotionRecord(item: PromotionItem, to: string, run: DeployRunI
657
723
  * release.
658
724
  */
659
725
  export function rollbackRecord(item: PromotionItem, env: string, run: DeployRunInfo): ReleaseRecordInput {
660
- return { ...sharedRecord(item, env, run), restores: refTo(item.source) };
726
+ return { ...sharedRecord(item, env, run), restores: refTo(item.source), ...releaseOf(item.source) };
661
727
  }
662
728
 
663
729
  /**
@@ -667,5 +733,5 @@ export function rollbackRecord(item: PromotionItem, env: string, run: DeployRunI
667
733
  * change, and the record says when it was last put back.
668
734
  */
669
735
  export function redeployRecord(item: PromotionItem, env: string, run: DeployRunInfo): ReleaseRecordInput {
670
- return { ...sharedRecord(item, env, run), redeploys: refTo(item.source) };
736
+ return { ...sharedRecord(item, env, run), redeploys: refTo(item.source), ...releaseOf(item.source) };
671
737
  }
@@ -28,7 +28,7 @@ import { spawn, spawnSync } from "node:child_process";
28
28
  import { existsSync, readFileSync, statSync } from "node:fs";
29
29
  import { isAbsolute, relative, resolve } from "node:path";
30
30
  import { loadChantConfig, type ChantConfig } from "../config";
31
- import { resolveCliBuildParams } from "../cli/build-params-cli";
31
+ import { resolveFlaggedBuildParams } from "../flag-params";
32
32
  import { discoverComponents } from "./discover";
33
33
  import { withoutDisabled } from "./cli-support";
34
34
  import { applyConfigDefaults } from "./config-defaults";
@@ -72,6 +72,13 @@ export interface CompiledTarget {
72
72
  components: string[];
73
73
  /** Each component's build-time steps, as declared for this environment, in composition order. */
74
74
  buildSteps: Record<string, unknown[]>;
75
+ /**
76
+ * Each component's publish steps (`PUBLISH_STEP_KINDS`), as declared for
77
+ * this environment, in composition order (#4027). The cut's dry run shows
78
+ * them as what each environment's deploy publishes. Absent from a compiler
79
+ * that does not report them.
80
+ */
81
+ publishSteps?: Record<string, unknown[]>;
75
82
  /** For each component, the files its build steps read, relative to the member's directory. Used to tell whether an artifact's source changed. */
76
83
  buildInputs: Record<string, string[]>;
77
84
  /** For each component, the file that declares it, relative to the member's directory. */
@@ -132,6 +139,11 @@ export function buildStepsOf(component: DriverComponent): DriverStep[] {
132
139
  return stepsOf(component.deploy).map((s) => s.step).filter((s) => BUILD_STEP_KINDS.includes(s.kind));
133
140
  }
134
141
 
142
+ /** A component's publish steps ({@link PUBLISH_STEP_KINDS}), each as declared, in composition order. */
143
+ export function publishStepsOf(component: DriverComponent): DriverStep[] {
144
+ return stepsOf(component.deploy).map((s) => s.step).filter((s) => PUBLISH_STEP_KINDS.includes(s.kind));
145
+ }
146
+
135
147
  /** The literal paths a component's archive steps read: `docker-build`'s `context` and `dockerfile`, `zip-package`'s `source`, `jvm-build`'s `path`. */
136
148
  export function buildInputPaths(component: DriverComponent): string[] {
137
149
  const out: string[] = [];
@@ -201,10 +213,11 @@ async function loadComponents(
201
213
  env: string,
202
214
  ): Promise<{ config: ChantConfig; params: ReleaseParam[]; declared: number; components: Array<{ component: DriverComponent; file: string }> }> {
203
215
  const { config } = await loadChantConfig(inputs.projectDir).catch(() => ({ config: {} as ChantConfig }));
204
- const resolved = resolveCliBuildParams(config.buildParams, {
216
+ // #4024: a workspace member's flag records back their parameters, so a flag's value is a param with source flag:<name>.
217
+ const resolved = await resolveFlaggedBuildParams(inputs.projectDir, config.buildParams, {
205
218
  ...(inputs.params ? { cli: inputs.params } : {}),
206
219
  ...(inputs.paramsFile ? { paramsFile: inputs.paramsFile.replaceAll("{env}", env) } : {}),
207
- });
220
+ }, env);
208
221
  if (!resolved.success) throw new ReleaseCompileError(`build parameters for "${env}" did not resolve:\n${resolved.errors.join("\n")}`);
209
222
  const discovered = await discoverComponents(inputs.projectDir, { buildParams: resolved.provenance });
210
223
  if (discovered.errors.length > 0) throw new ReleaseCompileError(discovered.errors.map((e) => e.message).join("\n"));
@@ -267,6 +280,7 @@ export async function compileEnvironment(inputs: CompileInputs, env: string): Pr
267
280
  params,
268
281
  components: components.map((c) => c.component.name),
269
282
  buildSteps: Object.fromEntries(components.map(({ component }) => [component.name, buildStepsOf(component)])),
283
+ publishSteps: Object.fromEntries(components.map(({ component }) => [component.name, publishStepsOf(component)])),
270
284
  buildInputs: Object.fromEntries(components.map(({ component }) => [component.name, buildInputPaths(component)])),
271
285
  componentFiles: Object.fromEntries(components.map(({ component, file }) => [component.name, rel(file)])),
272
286
  templates,
@@ -47,14 +47,16 @@
47
47
  * environment has no deploy of the component). When they are all equal the
48
48
  * member has no entries. When the environment's deploy names no release, or
49
49
  * the release it names can't be read, the member has a hole: nothing says
50
- * what the environment runs, so the change can't be shown empty.
50
+ * what the environment runs, so the change can't be shown empty. So does a
51
+ * component a promote, rollback or redeploy left failed or gated with nothing
52
+ * recorded since (#4035): its record names what ran before that run.
51
53
  */
52
54
 
53
55
  import type { CapabilityRegistry } from "./capability";
54
56
  import { deployContext, resolveComponentGraph, resolveStepInput, type DriverComponent, type DriverGate, type DriverPhase, type DriverStep } from "./driver";
55
57
  import { componentPlanDigest } from "./gate-plan";
56
58
  import { gateName, RELEASE_GATE_OP } from "../op/gate-name";
57
- import { capabilitySteps, readComponentOutputs } from "./wave-plan";
59
+ import { capabilitySteps, planContext, readComponentOutputs } from "./wave-plan";
58
60
  import {
59
61
  composeChangeSet,
60
62
  isEmptyChangeSet,
@@ -111,6 +113,12 @@ export interface ReleaseChangeSetInput {
111
113
  registry: CapabilityRegistry;
112
114
  /** Per component, what `env` runs now. A component missing here has never been deployed to it. */
113
115
  current: ReadonlyMap<string, CurrentDeploy>;
116
+ /**
117
+ * Components a promote, rollback or redeploy left failed or gated in `env`
118
+ * with nothing recorded since (#4035), each with why. What they run is not
119
+ * known, so each is a hole whatever its ledger record says.
120
+ */
121
+ unsettled?: ReadonlyMap<string, string>;
114
122
  /** Outputs supplied by the caller (`--seed-outputs`). */
115
123
  seededOutputs?: Record<string, Record<string, unknown>>;
116
124
  /** The chant version to record in the document. */
@@ -151,6 +159,8 @@ export function chantComponentPart(input: {
151
159
  env: string;
152
160
  digest: string;
153
161
  current: CurrentDeploy | undefined;
162
+ /** Why the component is unsettled in `env` (#4035), when it is. */
163
+ unsettled?: string;
154
164
  }): ChangeSetPart {
155
165
  const { component, release, env, digest, current } = input;
156
166
  const name = component.name;
@@ -184,6 +194,10 @@ export function chantComponentPart(input: {
184
194
  },
185
195
  ],
186
196
  });
197
+ if (input.unsettled !== undefined) {
198
+ member.holes.push({ address: name, type: RELEASE_COMPONENT_ENTRY_TYPE, reason: input.unsettled });
199
+ return { member, entries: [] };
200
+ }
187
201
  if (!current) {
188
202
  return entry("create", [
189
203
  { path: "artifact", after: digest },
@@ -238,7 +252,7 @@ export async function planReleaseChangeSet(input: ReleaseChangeSetInput): Promis
238
252
  const digest = input.digests.get(name) ?? "";
239
253
  const plannable = capabilitySteps(component.deploy).filter((s) => registry.has(s.kind) && registry.resolve(s.kind).plan !== undefined);
240
254
  if (plannable.length === 0) {
241
- parts.push(chantComponentPart({ component, release, env, digest, current: input.current.get(name) }));
255
+ parts.push(chantComponentPart({ component, release, env, digest, current: input.current.get(name), unsettled: input.unsettled?.get(name) }));
242
256
  continue;
243
257
  }
244
258
  const ctx = deployContext({ env }, name);
@@ -251,7 +265,7 @@ export async function planReleaseChangeSet(input: ReleaseChangeSetInput): Promis
251
265
  }
252
266
  for (const step of plannable) {
253
267
  const { kind, ...rest } = step;
254
- const planned = await registry.resolve(kind).plan!(ctx, resolveStepInput(rest, {}, outputs) as never);
268
+ const planned = await registry.resolve(kind).plan!(planContext(ctx, rest, outputs), resolveStepInput(rest, {}, outputs) as never);
255
269
  parts.push(
256
270
  planned.changeSet ?? {
257
271
  member: {
@@ -19,7 +19,7 @@
19
19
  * `@<component>.publish.*`) resolves.
20
20
  */
21
21
 
22
- import { resolveStepInput, type DriverComponent, type DriverPhase, type DriverStep } from "./driver";
22
+ import { resolveStepInput, unresolvedWiringSources, type DriverComponent, type DriverPhase, type DriverStep } from "./driver";
23
23
  import { componentPlanDigest } from "./gate-plan";
24
24
  import type { CapabilityRegistry, DeployContext } from "./capability";
25
25
  import type { WaveMember } from "../gated-waves";
@@ -60,6 +60,19 @@ export function capabilitySteps(phases: readonly DriverPhase[]): DriverStep[] {
60
60
  return out;
61
61
  }
62
62
 
63
+ /**
64
+ * The context a step is planned with: `ctx`, plus the components whose
65
+ * outputs the step reads and could not read yet (`unresolvedInputs`, #4084).
66
+ */
67
+ export function planContext(
68
+ ctx: DeployContext,
69
+ stepInput: Record<string, unknown>,
70
+ componentOutputs: Record<string, Record<string, unknown>>,
71
+ ): DeployContext {
72
+ const unresolved = unresolvedWiringSources(stepInput, componentOutputs);
73
+ return unresolved.length > 0 ? { ...ctx, unresolvedInputs: unresolved } : ctx;
74
+ }
75
+
63
76
  /** Plan one component against the outputs earlier waves produced. Never throws: a failure comes back as `error`. */
64
77
  export async function planWaveComponent(
65
78
  component: DriverComponent,
@@ -76,7 +89,7 @@ export async function planWaveComponent(
76
89
  if (!capability.plan) continue;
77
90
  const { kind: _kind, ...rest } = step;
78
91
  const input = resolveStepInput(rest, {}, componentOutputs);
79
- const planned = await capability.plan(ctx, input as never);
92
+ const planned = await capability.plan(planContext(ctx, rest, componentOutputs), input as never);
80
93
  const member = { member: planned.member, planDigest: planned.planDigest };
81
94
  members.push(member);
82
95
  parts.push(planned.changeSet ?? emptyPart(member));
@@ -122,3 +135,33 @@ export async function readComponentOutputs(
122
135
  }
123
136
  return found;
124
137
  }
138
+
139
+ /**
140
+ * Read the deployed outputs of each of `names` that `outputs` holds nothing
141
+ * for yet (#4104), through {@link readComponentOutputs}, and add them to
142
+ * `outputs`. A name that is not among `components`, or whose steps cannot
143
+ * say, is left out, so its dependents' references stay unresolved. Returns,
144
+ * per component read, the names of the outputs the read gave, sorted.
145
+ *
146
+ * `components` must be the whole graph, not the selection: the dependency a
147
+ * selected member reads is often one the change did not select.
148
+ */
149
+ export async function readDeployedOutputs(
150
+ names: readonly string[],
151
+ components: ReadonlyMap<string, DriverComponent>,
152
+ registry: CapabilityRegistry,
153
+ ctxOf: (component: string) => DeployContext,
154
+ outputs: Record<string, Record<string, unknown>>,
155
+ ): Promise<Record<string, string[]>> {
156
+ const read: Record<string, string[]> = {};
157
+ for (const name of names) {
158
+ if (name in outputs || name in read) continue;
159
+ const component = components.get(name);
160
+ if (!component) continue;
161
+ const found = await readComponentOutputs(component, registry, ctxOf(name), outputs);
162
+ if (!found) continue;
163
+ outputs[name] = found;
164
+ read[name] = Object.keys(found).sort();
165
+ }
166
+ return read;
167
+ }
package/src/config.ts CHANGED
@@ -12,6 +12,7 @@ import { findProjectConfig } from "./project-root";
12
12
  import { evaluateProjectConfig } from "./config-sandbox";
13
13
  import { lexiconNames, pathLexiconRoot, registerLexiconDeclarations, type LexiconDeclaration } from "./lexicon-module";
14
14
  import { decideConfigSchema, type DecideConfig } from "./op/decide-config";
15
+ import { PublishDeclarationSchema, type PublishDeclaration } from "./lifecycle/publish-declaration";
15
16
 
16
17
  /**
17
18
  * One project-declared environment (chant #1166). Historically always a bare
@@ -169,6 +170,7 @@ export const ChantConfigSchema = z.object({
169
170
  ).optional(),
170
171
  release: z.object({
171
172
  autoRecord: z.boolean().optional(),
173
+ publish: z.array(PublishDeclarationSchema).optional(),
172
174
  }).optional(),
173
175
  // Strict: a misspelt key here would leave Ops on the local runtime with
174
176
  // nothing said (#2523).
@@ -382,6 +384,12 @@ export interface ChantConfig {
382
384
  release?: {
383
385
  /** Set false to disable auto-emitting a release record after a successful component deploy project-wide. The `--no-release-record` CLI flag overrides this per-invocation. */
384
386
  autoRecord?: boolean;
387
+ /**
388
+ * Where `chant release publish` publishes a cut release (#4047): npm
389
+ * packages, an image in an OCI registry, release assets on a forge. Run in
390
+ * the order given. See ./lifecycle/publish-declaration.ts.
391
+ */
392
+ publish?: PublishDeclaration[];
385
393
  };
386
394
 
387
395
  /** Defaults for `chant run` and its subcommands (#2523). */