@intentius/chant 0.124.28 → 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 (395) 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 +15 -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 +40 -3
  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 +61 -10
  39. package/dist/cli/parse-args.d.ts.map +1 -1
  40. package/dist/cli/parse-args.js +24 -2
  41. package/dist/cli/registry.d.ts +15 -3
  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 +25 -0
  65. package/dist/components/pr-pipeline.d.ts.map +1 -1
  66. package/dist/components/pr-pipeline.js +22 -4
  67. package/dist/components/promote.d.ts +23 -0
  68. package/dist/components/promote.d.ts.map +1 -1
  69. package/dist/components/promote.js +60 -2
  70. package/dist/components/release-compile.d.ts +9 -0
  71. package/dist/components/release-compile.d.ts.map +1 -1
  72. package/dist/components/release-compile.js +9 -3
  73. package/dist/components/release-gate.d.ts +11 -1
  74. package/dist/components/release-gate.d.ts.map +1 -1
  75. package/dist/components/release-gate.js +10 -4
  76. package/dist/components/wave-plan.d.ts +5 -0
  77. package/dist/components/wave-plan.d.ts.map +1 -1
  78. package/dist/components/wave-plan.js +10 -2
  79. package/dist/config.d.ts +38 -0
  80. package/dist/config.d.ts.map +1 -1
  81. package/dist/config.js +2 -0
  82. package/dist/flag-params.d.ts +54 -0
  83. package/dist/flag-params.d.ts.map +1 -0
  84. package/dist/flag-params.js +130 -0
  85. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  86. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  87. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  88. package/dist/lifecycle/change-set-store.d.ts +2 -1
  89. package/dist/lifecycle/change-set-store.d.ts.map +1 -1
  90. package/dist/lifecycle/change-set-store.js +2 -1
  91. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  92. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  93. package/dist/lifecycle/delivery-metrics.js +145 -0
  94. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  95. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  96. package/dist/lifecycle/deploy-outcome.js +101 -0
  97. package/dist/lifecycle/gate-change-set.d.ts +10 -1
  98. package/dist/lifecycle/gate-change-set.d.ts.map +1 -1
  99. package/dist/lifecycle/gate-change-set.js +35 -3
  100. package/dist/lifecycle/gate-ledger.d.ts +2 -3
  101. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  102. package/dist/lifecycle/git.d.ts +30 -1
  103. package/dist/lifecycle/git.d.ts.map +1 -1
  104. package/dist/lifecycle/git.js +31 -5
  105. package/dist/lifecycle/index.d.ts +4 -0
  106. package/dist/lifecycle/index.d.ts.map +1 -1
  107. package/dist/lifecycle/index.js +4 -0
  108. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  109. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  110. package/dist/lifecycle/mixed-environment.js +154 -0
  111. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  112. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  113. package/dist/lifecycle/publish-attachments.js +179 -0
  114. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  115. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  116. package/dist/lifecycle/publish-credentials.js +69 -0
  117. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  118. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  119. package/dist/lifecycle/publish-declaration.js +83 -0
  120. package/dist/lifecycle/publish-forge.d.ts +53 -0
  121. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  122. package/dist/lifecycle/publish-forge.js +298 -0
  123. package/dist/lifecycle/publish-npm.d.ts +61 -0
  124. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  125. package/dist/lifecycle/publish-npm.js +242 -0
  126. package/dist/lifecycle/publish-oci.d.ts +64 -0
  127. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  128. package/dist/lifecycle/publish-oci.js +383 -0
  129. package/dist/lifecycle/publish-record.d.ts +66 -0
  130. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  131. package/dist/lifecycle/publish-record.js +84 -0
  132. package/dist/lifecycle/publish-targets.d.ts +45 -0
  133. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  134. package/dist/lifecycle/publish-targets.js +85 -0
  135. package/dist/lifecycle/release-cut.d.ts +48 -1
  136. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  137. package/dist/lifecycle/release-cut.js +116 -23
  138. package/dist/lifecycle/release-join.d.ts +95 -0
  139. package/dist/lifecycle/release-join.d.ts.map +1 -0
  140. package/dist/lifecycle/release-join.js +139 -0
  141. package/dist/lifecycle/release-publish.d.ts +312 -0
  142. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  143. package/dist/lifecycle/release-publish.js +335 -0
  144. package/dist/lifecycle/release-revocation.d.ts +80 -0
  145. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  146. package/dist/lifecycle/release-revocation.js +123 -0
  147. package/dist/lifecycle/release-store.d.ts +12 -2
  148. package/dist/lifecycle/release-store.d.ts.map +1 -1
  149. package/dist/lifecycle/release-store.js +4 -2
  150. package/dist/lifecycle/release-verify.d.ts +61 -0
  151. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  152. package/dist/lifecycle/release-verify.js +92 -0
  153. package/dist/lifecycle/release-workstation.d.ts +91 -0
  154. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  155. package/dist/lifecycle/release-workstation.js +180 -0
  156. package/dist/lifecycle/retention-policy.d.ts +45 -0
  157. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  158. package/dist/lifecycle/retention-policy.js +43 -0
  159. package/dist/lifecycle/retention.d.ts +185 -0
  160. package/dist/lifecycle/retention.d.ts.map +1 -0
  161. package/dist/lifecycle/retention.js +400 -0
  162. package/dist/plan-summary.d.ts.map +1 -1
  163. package/dist/plan-summary.js +8 -2
  164. package/dist/pr-loop.d.ts +7 -0
  165. package/dist/pr-loop.d.ts.map +1 -1
  166. package/dist/pr-loop.js +23 -4
  167. package/dist/pr-report-url.d.ts +82 -0
  168. package/dist/pr-report-url.d.ts.map +1 -0
  169. package/dist/pr-report-url.js +174 -0
  170. package/dist/provenance.d.ts +2 -2
  171. package/dist/provenance.d.ts.map +1 -1
  172. package/dist/run-event-store.d.ts +29 -2
  173. package/dist/run-event-store.d.ts.map +1 -1
  174. package/dist/run-event-store.js +50 -3
  175. package/dist/run-event-stream.d.ts +2 -1
  176. package/dist/run-event-stream.d.ts.map +1 -1
  177. package/dist/run-event-stream.js +5 -1
  178. package/dist/run-event.d.ts +19 -1
  179. package/dist/run-event.d.ts.map +1 -1
  180. package/dist/run-event.js +2 -0
  181. package/dist/wave-runtime.mjs +260 -229
  182. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  183. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  184. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  185. package/dist/workspace/change-set.schema.json +5 -1
  186. package/dist/workspace/check.schema.json +4 -2
  187. package/dist/workspace/checks/records.d.ts +9 -1
  188. package/dist/workspace/checks/records.d.ts.map +1 -1
  189. package/dist/workspace/checks/records.js +35 -1
  190. package/dist/workspace/checks.d.ts +1 -0
  191. package/dist/workspace/checks.d.ts.map +1 -1
  192. package/dist/workspace/checks.js +2 -1
  193. package/dist/workspace/ci-green.d.ts +8 -0
  194. package/dist/workspace/ci-green.d.ts.map +1 -1
  195. package/dist/workspace/ci-green.js +10 -3
  196. package/dist/workspace/declaration.d.ts +16 -0
  197. package/dist/workspace/declaration.d.ts.map +1 -1
  198. package/dist/workspace/declaration.js +26 -0
  199. package/dist/workspace/declaration.schema.json +64 -0
  200. package/dist/workspace/declared-kinds.d.ts +16 -0
  201. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  202. package/dist/workspace/declared-kinds.js +25 -0
  203. package/dist/workspace/delivery-metrics.d.ts +91 -0
  204. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  205. package/dist/workspace/delivery-metrics.js +217 -0
  206. package/dist/workspace/delivery-metrics.schema.json +519 -0
  207. package/dist/workspace/flags-read.d.ts +37 -0
  208. package/dist/workspace/flags-read.d.ts.map +1 -0
  209. package/dist/workspace/flags-read.js +103 -0
  210. package/dist/workspace/flags.d.ts +100 -0
  211. package/dist/workspace/flags.d.ts.map +1 -0
  212. package/dist/workspace/flags.js +98 -0
  213. package/dist/workspace/graph-cli.js +1 -1
  214. package/dist/workspace/intent-cli.d.ts +11 -2
  215. package/dist/workspace/intent-cli.d.ts.map +1 -1
  216. package/dist/workspace/intent-cli.js +54 -5
  217. package/dist/workspace/intent-trace.d.ts +219 -0
  218. package/dist/workspace/intent-trace.d.ts.map +1 -0
  219. package/dist/workspace/intent-trace.js +314 -0
  220. package/dist/workspace/intent.d.ts +30 -6
  221. package/dist/workspace/intent.d.ts.map +1 -1
  222. package/dist/workspace/intent.js +141 -21
  223. package/dist/workspace/intent.schema.json +702 -6
  224. package/dist/workspace/overlap.d.ts +146 -0
  225. package/dist/workspace/overlap.d.ts.map +1 -0
  226. package/dist/workspace/overlap.js +276 -0
  227. package/dist/workspace/reason-codes.d.ts +2 -0
  228. package/dist/workspace/reason-codes.d.ts.map +1 -1
  229. package/dist/workspace/reason-codes.js +3 -0
  230. package/dist/workspace/records.d.ts +15 -2
  231. package/dist/workspace/records.d.ts.map +1 -1
  232. package/dist/workspace/records.js +32 -0
  233. package/dist/workspace/records.schema.json +21 -2
  234. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  235. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  236. package/dist/workspace/releases.d.ts +9 -0
  237. package/dist/workspace/releases.d.ts.map +1 -1
  238. package/dist/workspace/releases.js +9 -0
  239. package/dist/workspace/releases.schema.json +123 -1
  240. package/dist/workspace/run-event.schema.json +10 -3
  241. package/dist/workspace/status-gates.d.ts +8 -0
  242. package/dist/workspace/status-gates.d.ts.map +1 -1
  243. package/dist/workspace/status.d.ts +78 -0
  244. package/dist/workspace/status.d.ts.map +1 -1
  245. package/dist/workspace/status.js +170 -3
  246. package/dist/workspace/status.schema.json +198 -1
  247. package/dist/workspace/wip.d.ts.map +1 -1
  248. package/dist/workspace/wip.js +11 -3
  249. package/package.json +1 -1
  250. package/src/build-params.test.ts +46 -0
  251. package/src/build-params.ts +55 -1
  252. package/src/change-set-graph.ts +10 -2
  253. package/src/change-set.ts +14 -3
  254. package/src/cli/build-params-cli.ts +22 -9
  255. package/src/cli/commands/build.ts +6 -3
  256. package/src/cli/handlers/approve-release.test.ts +5 -1
  257. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  258. package/src/cli/handlers/approve.ts +47 -0
  259. package/src/cli/handlers/build.test.ts +15 -0
  260. package/src/cli/handlers/build.ts +17 -4
  261. package/src/cli/handlers/fan-out.test.ts +180 -1
  262. package/src/cli/handlers/fan-out.ts +38 -3
  263. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  264. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  265. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  266. package/src/cli/handlers/pr.test.ts +148 -1
  267. package/src/cli/handlers/pr.ts +139 -28
  268. package/src/cli/handlers/promote.test.ts +280 -2
  269. package/src/cli/handlers/promote.ts +210 -34
  270. package/src/cli/handlers/release-implicit.e2e.test.ts +12 -4
  271. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  272. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  273. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  274. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  275. package/src/cli/handlers/release.e2e.test.ts +146 -32
  276. package/src/cli/handlers/release.ts +415 -28
  277. package/src/cli/handlers/run-auto-release.test.ts +9 -0
  278. package/src/cli/handlers/run-release.test.ts +55 -0
  279. package/src/cli/handlers/run.ts +69 -5
  280. package/src/cli/main.test.ts +1 -0
  281. package/src/cli/main.ts +61 -10
  282. package/src/cli/parse-args.ts +19 -2
  283. package/src/cli/registry.ts +15 -3
  284. package/src/cli/run-events.ts +5 -2
  285. package/src/components/auto-release.test.ts +9 -0
  286. package/src/components/auto-release.ts +3 -0
  287. package/src/components/capability.ts +9 -0
  288. package/src/components/driver.test.ts +24 -0
  289. package/src/components/driver.ts +36 -0
  290. package/src/components/fan-out-inputs.test.ts +85 -0
  291. package/src/components/fan-out-inputs.ts +192 -0
  292. package/src/components/fan-out-record.test.ts +10 -0
  293. package/src/components/fan-out-record.ts +19 -0
  294. package/src/components/fan-out-run.ts +48 -0
  295. package/src/components/fan-out.ts +4 -0
  296. package/src/components/pr-pipeline.ts +35 -4
  297. package/src/components/promote.ts +68 -2
  298. package/src/components/release-compile.ts +17 -3
  299. package/src/components/release-gate.ts +18 -4
  300. package/src/components/wave-plan.ts +15 -2
  301. package/src/config.ts +8 -0
  302. package/src/flag-params.test.ts +144 -0
  303. package/src/flag-params.ts +134 -0
  304. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  305. package/src/lifecycle/change-set-store.ts +2 -1
  306. package/src/lifecycle/delivery-metrics.ts +263 -0
  307. package/src/lifecycle/deploy-outcome.ts +147 -0
  308. package/src/lifecycle/gate-change-set.ts +37 -6
  309. package/src/lifecycle/gate-ledger.ts +2 -2
  310. package/src/lifecycle/git.ts +61 -8
  311. package/src/lifecycle/index.ts +4 -0
  312. package/src/lifecycle/mixed-environment.test.ts +97 -0
  313. package/src/lifecycle/mixed-environment.ts +204 -0
  314. package/src/lifecycle/publish-attachments.ts +196 -0
  315. package/src/lifecycle/publish-credentials.ts +84 -0
  316. package/src/lifecycle/publish-declaration.ts +95 -0
  317. package/src/lifecycle/publish-forge.ts +325 -0
  318. package/src/lifecycle/publish-npm.ts +259 -0
  319. package/src/lifecycle/publish-oci.ts +396 -0
  320. package/src/lifecycle/publish-record.ts +113 -0
  321. package/src/lifecycle/publish-resume.test.ts +168 -0
  322. package/src/lifecycle/publish-targets.test.ts +494 -0
  323. package/src/lifecycle/publish-targets.ts +100 -0
  324. package/src/lifecycle/release-cut.test.ts +74 -2
  325. package/src/lifecycle/release-cut.ts +158 -22
  326. package/src/lifecycle/release-flag.test.ts +112 -0
  327. package/src/lifecycle/release-join.test.ts +68 -0
  328. package/src/lifecycle/release-join.ts +182 -0
  329. package/src/lifecycle/release-publish.test.ts +225 -0
  330. package/src/lifecycle/release-publish.ts +568 -0
  331. package/src/lifecycle/release-revocation.ts +147 -0
  332. package/src/lifecycle/release-store.ts +15 -4
  333. package/src/lifecycle/release-verify.test.ts +110 -0
  334. package/src/lifecycle/release-verify.ts +120 -0
  335. package/src/lifecycle/release-workstation.test.ts +204 -0
  336. package/src/lifecycle/release-workstation.ts +216 -0
  337. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  338. package/src/lifecycle/retention-policy.ts +63 -0
  339. package/src/lifecycle/retention.test.ts +363 -0
  340. package/src/lifecycle/retention.ts +508 -0
  341. package/src/plan-summary.test.ts +20 -0
  342. package/src/plan-summary.ts +8 -1
  343. package/src/pr-loop.ts +25 -4
  344. package/src/pr-report-url.test.ts +131 -0
  345. package/src/pr-report-url.ts +209 -0
  346. package/src/provenance.ts +2 -2
  347. package/src/run-event-store.ts +63 -3
  348. package/src/run-event-stream.ts +4 -1
  349. package/src/run-event.ts +21 -1
  350. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  351. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  352. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  353. package/src/workspace/change-set.schema.json +5 -1
  354. package/src/workspace/check.schema.json +4 -2
  355. package/src/workspace/checks/records.ts +35 -1
  356. package/src/workspace/checks.ts +2 -1
  357. package/src/workspace/ci-green.ts +14 -3
  358. package/src/workspace/declaration.schema.json +64 -0
  359. package/src/workspace/declaration.test.ts +28 -0
  360. package/src/workspace/declaration.ts +38 -0
  361. package/src/workspace/declared-kinds.ts +40 -1
  362. package/src/workspace/delivery-metrics.schema.json +519 -0
  363. package/src/workspace/delivery-metrics.test.ts +236 -0
  364. package/src/workspace/delivery-metrics.ts +286 -0
  365. package/src/workspace/flags-read.ts +121 -0
  366. package/src/workspace/flags.test.ts +141 -0
  367. package/src/workspace/flags.ts +157 -0
  368. package/src/workspace/graph-cli.ts +1 -1
  369. package/src/workspace/intent-cli.ts +46 -5
  370. package/src/workspace/intent-trace.test.ts +173 -0
  371. package/src/workspace/intent-trace.ts +484 -0
  372. package/src/workspace/intent.schema.json +702 -6
  373. package/src/workspace/intent.ts +148 -25
  374. package/src/workspace/older-records.test.ts +412 -0
  375. package/src/workspace/overlap.test.ts +165 -0
  376. package/src/workspace/overlap.ts +340 -0
  377. package/src/workspace/read-contract.e2e.test.ts +2 -1
  378. package/src/workspace/reason-codes.test.ts +5 -0
  379. package/src/workspace/reason-codes.ts +3 -0
  380. package/src/workspace/record-assets.test.ts +2 -1
  381. package/src/workspace/records-contract.e2e.test.ts +2 -1
  382. package/src/workspace/records.schema.json +21 -2
  383. package/src/workspace/records.ts +36 -2
  384. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  385. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  386. package/src/workspace/releases.schema.json +123 -1
  387. package/src/workspace/releases.ts +11 -0
  388. package/src/workspace/run-event.schema.json +10 -3
  389. package/src/workspace/status-contract.test.ts +29 -0
  390. package/src/workspace/status-gates.ts +8 -0
  391. package/src/workspace/status-mixed.test.ts +152 -0
  392. package/src/workspace/status-revoked.test.ts +153 -0
  393. package/src/workspace/status.schema.json +198 -1
  394. package/src/workspace/status.ts +254 -3
  395. package/src/workspace/wip.ts +11 -2
@@ -15,7 +15,7 @@ import { pointAnswerCommand, renderHuman, renderJson } from "../../op/local-outp
15
15
  import { loadPlugins } from "../plugins";
16
16
  import { recordGateApproval } from "./operator";
17
17
  import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format";
18
- import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
18
+ import { parseParamFlags } from "../build-params-cli";
19
19
  import type { CommandContext } from "../registry";
20
20
  import { stewardBesideFor, stewardFormFor, stewardTurnLeaseName, DEFAULT_STEWARD_ENV, type StewardDeclaration } from "../../op/steward";
21
21
  import { holdBesideLease } from "../../op/steward-beside";
@@ -470,7 +470,13 @@ async function runOpLogOnRuntime(ctx: CommandContext, name: string): Promise<num
470
470
  export function humanRunEventLine(e: RunEvent): string {
471
471
  const x = e as unknown as Record<string, unknown>;
472
472
  const where = [x.component, x.phase, x.step].filter((v): v is string => typeof v === "string").join("/");
473
- const detail = [where, typeof x.status === "string" ? x.status : "", typeof x.error === "string" ? x.error : ""].filter(Boolean).join(" ");
473
+ const detail = [
474
+ where,
475
+ typeof x.status === "string" ? x.status : "",
476
+ // A blocked component (#4007) names what held it back.
477
+ typeof x.blockedBy === "string" ? `by ${x.blockedBy}` : "",
478
+ typeof x.error === "string" ? x.error : "",
479
+ ].filter(Boolean).join(" ");
474
480
  return `${String(x.at ?? "")} ${String(x.type).padEnd(16)}${detail}`.trimEnd();
475
481
  }
476
482
 
@@ -926,6 +932,7 @@ export async function recordAutoReleasesForRun(
926
932
  runId: string,
927
933
  disabled: boolean,
928
934
  runOrigin: RunOrigin = { forge: "local" },
935
+ releaseDigest?: string,
929
936
  ): Promise<ReleaseRecord[]> {
930
937
  const recorded: ReleaseRecord[] = [];
931
938
  for (const componentResult of results) {
@@ -942,6 +949,8 @@ export async function recordAutoReleasesForRun(
942
949
  // #1035, #4020: who approved at the gate and which resolution admitted the deploy.
943
950
  ...(gateApprover(componentResult) ? { approver: gateApprover(componentResult) } : {}),
944
951
  ...(gateAdmission(componentResult) ? { admission: gateAdmission(componentResult) } : {}),
952
+ // #4043: the release the deploy was made under, when `--release` named one.
953
+ ...(releaseDigest !== undefined ? { releaseDigest } : {}),
945
954
  },
946
955
  { disabled },
947
956
  );
@@ -1051,6 +1060,44 @@ function refusesNoRecordOn(runtime: Pick<OpRuntimeProvider, "name">): boolean {
1051
1060
  return true;
1052
1061
  }
1053
1062
 
1063
+ /**
1064
+ * Resolve the release `chant run --components --release` names (#4043), or the
1065
+ * reason it can't be used. The run builds from the checked-out commit, so the
1066
+ * release's commit must be HEAD, and the release needs a target for `env`.
1067
+ */
1068
+ export async function resolveRunRelease(ref: string, env: string, cwd: string) {
1069
+ const { fetchLifecycle, getHeadCommit } = await import("../../lifecycle/git");
1070
+ const { resolveRelease } = await import("../../lifecycle/release-store");
1071
+ const { targetFor } = await import("../../components/release-promote");
1072
+ await fetchLifecycle({ cwd }).catch(() => false);
1073
+ const found = await resolveRelease(ref, { cwd });
1074
+ if (!found) return `no release is labelled or digested "${ref}" in this member`;
1075
+ const name = found.label ?? found.release.releaseDigest;
1076
+ const head = await getHeadCommit({ cwd });
1077
+ if (found.release.commit !== head) {
1078
+ return `release ${name} was cut from ${found.release.commit.slice(0, 12)} and this checkout is at ${head.slice(0, 12)}; run builds from the checkout, so check out the release's commit, or deploy it with \`chant components promote --release\``;
1079
+ }
1080
+ const target = targetFor(found.release, env);
1081
+ if ("error" in target) return target.error;
1082
+ return { releaseDigest: found.release.releaseDigest, label: found.label, release: found.release };
1083
+ }
1084
+
1085
+ /** A rebuilt image need not digest as the release's own did; say so, as promote does. Never fails the run. */
1086
+ async function warnOnArtifactMismatch(release: import("../../lifecycle/release-store").Release, recorded: readonly ReleaseRecord[]): Promise<void> {
1087
+ try {
1088
+ const { releaseArtifactMismatches } = await import("../../components/release-promote");
1089
+ const { readBuildManifest } = await import("../../lifecycle/build-ledger-store");
1090
+ const mismatches = await releaseArtifactMismatches(
1091
+ release,
1092
+ recorded.map((r) => ({ component: r.component, source: r })),
1093
+ (digest) => readBuildManifest(digest).catch(() => null),
1094
+ );
1095
+ for (const message of mismatches) console.error(formatWarning({ message }));
1096
+ } catch {
1097
+ // The record is written; a manifest that can't be read is not worth failing the run for.
1098
+ }
1099
+ }
1100
+
1054
1101
  async function runComponentSelection(ctx: CommandContext): Promise<number> {
1055
1102
  const selector = ctx.args.path;
1056
1103
  if (!selector || selector === ".") {
@@ -1066,11 +1113,13 @@ async function runComponentSelection(ctx: CommandContext): Promise<number> {
1066
1113
 
1067
1114
  const projectPath = resolve(".");
1068
1115
  const { config } = await loadChantConfig(projectPath).catch(() => ({ config: {} as ChantConfig }));
1069
- const paramsResolution = resolveCliBuildParams(config.buildParams, {
1116
+ // #4024: with --env, a workspace member's flag records back their parameters.
1117
+ const { resolveFlaggedBuildParams } = await import("../../flag-params");
1118
+ const paramsResolution = await resolveFlaggedBuildParams(projectPath, config.buildParams, {
1070
1119
  cli: parseParamFlags(ctx.args.param),
1071
1120
  paramsFile: ctx.args.paramsFile,
1072
1121
  verbose: ctx.args.verbose,
1073
- });
1122
+ }, ctx.args.env);
1074
1123
  if (!paramsResolution.success) {
1075
1124
  for (const message of paramsResolution.errors) console.error(message);
1076
1125
  return 1;
@@ -1088,6 +1137,20 @@ async function runComponentSelection(ctx: CommandContext): Promise<number> {
1088
1137
  }
1089
1138
 
1090
1139
  const env = ctx.args.env ?? "local";
1140
+
1141
+ // #4043: `--release <label|digest>` says this run deploys that release. The
1142
+ // run builds from the checked-out commit, so the release must have been cut
1143
+ // from it, and must have a target for the environment. Both are checked
1144
+ // before anything deploys; nothing is inferred when the flag is absent.
1145
+ let underRelease: { releaseDigest: string; label: string | null; release: import("../../lifecycle/release-store").Release } | undefined;
1146
+ if (ctx.args.release) {
1147
+ const refused = await resolveRunRelease(ctx.args.release, env, projectPath);
1148
+ if (typeof refused === "string") {
1149
+ console.error(formatError({ message: refused, hint: "List the releases with: chant release list" }));
1150
+ return 1;
1151
+ }
1152
+ underRelease = refused;
1153
+ }
1091
1154
  // Seed cross-component/cross-stack outputs from upstream jobs' dumped files
1092
1155
  // (`--seed-outputs`), so a single-component run resolves references to a
1093
1156
  // component that ran in an earlier CI job.
@@ -1207,7 +1270,8 @@ async function runComponentSelection(ctx: CommandContext): Promise<number> {
1207
1270
  if (result.success && result.run) {
1208
1271
  // #3955: a run that records nothing has no release to report either.
1209
1272
  const disabled = resolveAutoReleaseDisabled(config, ctx.args.noReleaseRecord) || ledgerRecordingOff();
1210
- const recorded = await recordAutoReleasesForRun(result.run.results, env, release.runId, disabled, release.runOrigin);
1273
+ const recorded = await recordAutoReleasesForRun(result.run.results, env, release.runId, disabled, release.runOrigin, underRelease?.releaseDigest);
1274
+ if (underRelease) await warnOnArtifactMismatch(underRelease.release, recorded);
1211
1275
  // `--digest-file` (#2602): the releases this run recorded, one
1212
1276
  // `<component>=<digest>` line each, the form `chant components promote
1213
1277
  // --digest` takes. A generated promote job reads it to promote exactly
@@ -828,6 +828,7 @@ describe("workspace init and ls (#2534)", () => {
828
828
  "workspace ls",
829
829
  "workspace member",
830
830
  "workspace member-run",
831
+ "workspace metrics",
831
832
  "workspace patch",
832
833
  "workspace pin",
833
834
  "workspace points",
package/src/cli/main.ts CHANGED
@@ -263,12 +263,18 @@ CI:
263
263
  signs its tags with.
264
264
 
265
265
  Releases:
266
- release cut [<label>] [--build <command>] [--param <k=v>] [--params-file <file>]
267
- [--src <dir>] [-o <file>] [--lexicon <name>] [--sign [<key file>]]
268
- Cut a release of this member from HEAD, which must
269
- carry a ci/green tag: compile every environment in
270
- the config's environments, build the artifacts once,
271
- and store it on chant/lifecycle under its digest.
266
+ release cut [<label>] [--yes] [--build <command>] [--param <k=v>]
267
+ [--params-file <file>] [--src <dir>] [-o <file>] [--lexicon <name>]
268
+ [--actor <name>] [--sign [<key file>]] [--json]
269
+ A dry run of a cut of this member from HEAD, which
270
+ must be clean, on trunk and pushed, and carry a
271
+ ci/green tag: compile every environment in the
272
+ config's environments and print what the cut would
273
+ build, record and tag, and what publishes where.
274
+ --yes cuts: builds the artifacts once, stores the
275
+ release on chant/lifecycle under its digest, records
276
+ the actor and run id, tags the commit release/<label>
277
+ here, and prints the next command.
272
278
  --build runs before each environment's templates are
273
279
  read, with CHANT_ENV set; {env} in --params-file is
274
280
  replaced by the environment. --sign signs the cut with
@@ -277,6 +283,21 @@ Releases:
277
283
  what chant build --env <env> writes: --src <dir> is
278
284
  the build root, -o <file> the output (dist/build.yaml),
279
285
  --lexicon narrows it.
286
+ release publish <label|digest> [--yes] [--allow-unsigned]
287
+ [--require-provenance] [--json]
288
+ Publish a cut release to the targets release.publish
289
+ in chant.config.ts declares (npm, OCI registries,
290
+ forge release assets), each with an SBOM and the
291
+ release's provenance. A dry run without --yes.
292
+ Refuses a revoked release, one whose signature does
293
+ not verify against .chant/allowed_signers unless
294
+ --allow-unsigned, and with a target a checkout that
295
+ is not the release's commit. Warns first when the run
296
+ has no OIDC token, so what it publishes carries no
297
+ OIDC provenance; --require-provenance refuses instead.
298
+ release verify <label|digest> [--json]
299
+ Check, offline, that the release was signed by a key
300
+ .chant/allowed_signers lists at the release's commit.
280
301
  release list [--json] Every release of this member with its targets, its
281
302
  cuts and the environments that run it.
282
303
  release export <label|digest> --env <env> [-o <dir>] [--json]
@@ -344,7 +365,9 @@ Ops:
344
365
  run, and prints that order first (#3950).
345
366
  On success, auto-emits a release-ledger record per
346
367
  component that published a digest (default: on;
347
- --no-release-record to opt out; #597)
368
+ --no-release-record to opt out; #597).
369
+ --release <label|digest>: the run is of that release,
370
+ cut from this checkout's commit; the records name it (#4043)
348
371
  --progress-json: stream one NDJSON RunProgressEvent
349
372
  per line to stdout while the run executes, for a
350
373
  consumer to render live wave/component/phase/step
@@ -443,6 +466,13 @@ Workspace (level 1, #2524):
443
466
  beside it and marks the members whose digests differ.
444
467
  Read only; never fetches. --json prints the
445
468
  read-contract document
469
+ workspace metrics [dir] [--env <env>] [--member <name>] [--since <date>] [--json]
470
+ The four DORA metrics per member and environment
471
+ (#4039): deployment frequency, lead time from commit
472
+ to running release, change failure rate and time to
473
+ restore, from the deploy records, releases and ci
474
+ revoked tags. Eight weeks back by default. Read only;
475
+ never fetches. --json prints the read-contract document
446
476
  workspace records [--kind <kind file>] [--current] [--uncommitted | --at <rev>] [--base <rev>] [--require attested] [--json]
447
477
  Without --kind, every record kind the declaration
448
478
  names. Read the records a record kind locates, validated
@@ -757,7 +787,7 @@ Workspace (level 1, #2524):
757
787
  Each composite instance the members declare, with the
758
788
  components whose contract can deploy it; an instance
759
789
  with none lists an empty set
760
- workspace graph --intent <path[:start-end]|path#symbol> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]
790
+ workspace graph --intent <path[:start-end]|path#symbol|member/node|sha> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]
761
791
  The intent graph over one region: the commits that
762
792
  touched it, the decisions whose constrains cover it,
763
793
  the artifacts they pin, and findings with closed codes.
@@ -796,6 +826,13 @@ Lifecycle (alias: lc):
796
826
  executes the plan (production-like names also need
797
827
  --confirm-prod, or an interactive confirmation)
798
828
  lifecycle log [env] History of lifecycle snapshots
829
+ lifecycle gc [--yes] [--push] [--json]
830
+ Prune stored change sets, releases and run event
831
+ files by the retention policy (#4036). A dry run
832
+ without --yes; --yes writes one commit removing
833
+ them on the local chant/lifecycle and deletes the
834
+ run event files; --push pushes it. History is
835
+ never rewritten, and the ledgers are never pruned
799
836
 
800
837
  Plan scenarios (#1292):
801
838
  scenario check Evaluate every declared Scenario's expect clause
@@ -834,7 +871,11 @@ Component release ledger + status:
834
871
  --member <name>, both commands: a workspace
835
872
  member's loop, gate op pr-<n>-<member>;
836
873
  --resume <file> finishes an apply that failed
837
- partway under the same approval)
874
+ partway under the same approval;
875
+ --events-json, both commands: the run-event
876
+ stream on stdout; --report-url <s3 or file URL>,
877
+ both commands: copy the report, the change set
878
+ and the events to <url>/<pr>/<commit>/)
838
879
  components release <env> Append one immutable release record
839
880
  (--component <name> --digest <sha256:...>
840
881
  [--git-sha <sha>] [--run-id <id>] [--actor <name>]);
@@ -858,7 +899,9 @@ Component release ledger + status:
858
899
  components rollback <env> Redeploy an earlier release of an environment
859
900
  from its recorded digest (--component <name>
860
901
  [--digest <sha256:...>]; defaults to the release
861
- before the current one; --dry-run prints the plan)
902
+ before the current one; --dry-run prints the plan).
903
+ --release <label|digest> restores every component
904
+ of that release through the release gate (#4035)
862
905
  components redeploy <env> Redeploy the release an environment's ledger
863
906
  records as current, after a deploy that failed
864
907
  partway (--component <name> [--digest <sha256:...>];
@@ -1265,6 +1308,10 @@ export const commandRegistry: CommandDef[] = [
1265
1308
  { name: "ci workflow", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/ci-green-cli")).runCiWorkflow(ctx) },
1266
1309
  // #4026: the release cut from a green commit (ws-104). Imported on first use.
1267
1310
  { name: "release cut", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/release")).runReleaseCut(ctx) },
1311
+ // #4027: publish a cut release, to the targets #4047 declares.
1312
+ { name: "release publish", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/release")).runReleasePublish(ctx) },
1313
+ // #4049: check a release's signature offline.
1314
+ { name: "release verify", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/release")).runReleaseVerify(ctx) },
1268
1315
  { name: "release list", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/release")).runReleaseList(ctx) },
1269
1316
  { name: "release export", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/release")).runReleaseExport(ctx) },
1270
1317
  { name: "release compile", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/release")).runReleaseCompile(ctx) },
@@ -1323,6 +1370,8 @@ export const commandRegistry: CommandDef[] = [
1323
1370
  { name: "workspace work", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/work-cli")).runWorkspaceWork(ctx) },
1324
1371
  // #3033 — the agent run record: what each run cost and which commits it made. chant records runs and never starts one.
1325
1372
  { name: "workspace runs", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/runs-cli")).runWorkspaceRuns(ctx) },
1373
+ // #4039 — DORA metrics from the deploy, release and revocation records.
1374
+ { name: "workspace metrics", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/delivery-metrics")).runWorkspaceMetrics(ctx) },
1326
1375
  { name: "workspace status", runsNoConfig: true, handler: async (ctx) => (await import("../workspace/status")).runWorkspaceStatus(ctx) },
1327
1376
  { name: "workspace lineage", handler: async (ctx) => (await import("../workspace/lineage-cli")).runWorkspaceLineage(ctx) },
1328
1377
  { name: "workspace upgrade", handler: async (ctx) => (await import("../workspace/lineage-upgrade-cli")).runWorkspaceUpgrade(ctx) },
@@ -1373,6 +1422,8 @@ export const commandRegistry: CommandDef[] = [
1373
1422
  { name: "lifecycle whoami", requiresPlugins: true, handler: runLifecycleWhoami },
1374
1423
  { name: "lifecycle teardown", requiresPlugins: true, handler: runLifecycleTeardown },
1375
1424
  { name: "lifecycle log", handler: runLifecycleLog },
1425
+ // #4036 — retention's garbage collection. Imported on first use.
1426
+ { name: "lifecycle gc", runsNoConfig: true, handler: async (ctx) => (await import("./handlers/lifecycle-gc")).runLifecycleGc(ctx) },
1376
1427
 
1377
1428
  // Plan scenarios (#1292) — fully offline, no plugin network calls, but
1378
1429
  // requiresPlugins:true so the build step has serializers to partition
@@ -74,12 +74,15 @@ const BOOLEAN_FLAGS = new Set([
74
74
  "--uncommitted",
75
75
  "--allow-code",
76
76
  "--allow-skip",
77
+ "--allow-unsigned",
78
+ "--require-provenance",
77
79
  "--root-only",
78
80
  "--generated",
79
81
  "--decide",
80
82
  "--record-plans",
81
83
  "--loopback",
82
84
  "--artifacts",
85
+ "--push",
83
86
  ]);
84
87
 
85
88
  /**
@@ -248,6 +251,9 @@ export function parseArgs(args: string[]): ParsedArgs {
248
251
  result.dryRun = true;
249
252
  } else if (arg === "--yes") {
250
253
  result.yes = true;
254
+ } else if (arg === "--push") {
255
+ // `chant lifecycle gc --yes --push` (#4036): push the prune commit.
256
+ result.push = true;
251
257
  } else if (arg === "--confirm-prod") {
252
258
  result.confirmProd = true;
253
259
  } else if (arg === "--strict") {
@@ -375,6 +381,17 @@ export function parseArgs(args: string[]): ParsedArgs {
375
381
  if (!result.forge || result.forge.startsWith("-")) throw new Error("--forge needs a forge: --forge github|gitlab|forgejo");
376
382
  } else if (arg === "--require-review") {
377
383
  result.requireReview = true;
384
+ } else if (arg === "--report-url") {
385
+ // `chant components pr-plan|pr-apply --report-url <url>` (#4007).
386
+ const url = args[++i];
387
+ if (!url || url.startsWith("-")) throw new Error("--report-url needs a URL: --report-url s3://<bucket>/<prefix> or file://<directory>");
388
+ result.reportUrl = url;
389
+ } else if (arg === "--allow-unsigned") {
390
+ // `chant release publish --allow-unsigned` (#4049).
391
+ result.allowUnsigned = true;
392
+ } else if (arg === "--require-provenance") {
393
+ // `chant release publish --require-provenance` (#4049).
394
+ result.requireProvenance = true;
378
395
  } else if (arg === "--pr-loop") {
379
396
  result.prLoop = true;
380
397
  } else if (arg === "--before-script") {
@@ -427,11 +444,11 @@ export function parseArgs(args: string[]): ParsedArgs {
427
444
  // `chant workspace graph --composites` (#2662)
428
445
  result.composites = true;
429
446
  } else if (arg === "--intent") {
430
- // `chant workspace graph --intent <path[:start-end]|path#symbol>` (#2651), or `--intent --record <id>` with no region.
447
+ // `chant workspace graph --intent <path[:start-end]|path#symbol|member/node|sha>` (#2651), or `--intent --record <id>` with no region.
431
448
  const value = args[i + 1];
432
449
  if (value !== undefined && !value.startsWith("-")) result.intent = args[++i];
433
450
  else if (args.includes("--record")) result.intent = "";
434
- else throw new Error("--intent needs a region: --intent <path[:start-end]|path#symbol>, or --intent --record <id>");
451
+ else throw new Error("--intent needs a region: --intent <path[:start-end]|path#symbol|member/node|sha>, or --intent --record <id>");
435
452
  } else if (arg === "--follow-squash") {
436
453
  // `chant workspace graph --intent ... --follow-squash` and `chant workspace runs --follow-squash` (#3035)
437
454
  result.followSquash = true;
@@ -149,6 +149,10 @@ export interface ParsedArgs {
149
149
  allowCode?: boolean;
150
150
  /** `chant components promote --allow-skip` (#4030): promote past a step of the member's declared promotion order. */
151
151
  allowSkip?: boolean;
152
+ /** `chant release publish --allow-unsigned` (#4049): publish a release with no signature that verifies, with a warning. Never overrides identity.gates.release. */
153
+ allowUnsigned?: boolean;
154
+ /** `chant release publish --require-provenance` (#4049): refuse a run with no OIDC token, where it warns. */
155
+ requireProvenance?: boolean;
152
156
  /** `chant workspace upgrade --source <repo>[#<member>]` (#2551): move the scope to another template. */
153
157
  source?: string;
154
158
  /** `chant workspace adopt-lineage --tags <glob>` and `hash-index --tags` (#2551): the tags to compare. */
@@ -164,6 +168,8 @@ export interface ParsedArgs {
164
168
  /** `chant lifecycle teardown <env> --yes` — execute the planned deletion
165
169
  * (#1222). Without it the command plans and stops. */
166
170
  yes?: boolean;
171
+ /** `chant lifecycle gc --yes --push` (#4036): push the commit the prune wrote to chant/lifecycle. Without it the prune stays local. */
172
+ push?: boolean;
167
173
  /** `chant lifecycle teardown <env> --yes --confirm-prod` — the non-interactive
168
174
  * form of the extra confirmation a production-like environment name demands
169
175
  * (#1222). Meaningless without `--yes`. */
@@ -308,6 +314,12 @@ export interface ParsedArgs {
308
314
  * request. Needs `--forge`.
309
315
  */
310
316
  requireReview?: boolean;
317
+ /**
318
+ * `chant components pr-plan|pr-apply --report-url <url>` (#4007) — copy
319
+ * the stage's report, change set and run events to `<url>/<pr>/<commit>/`,
320
+ * an `s3://` or `file://` URL, the events as each wave and component settles.
321
+ */
322
+ reportUrl?: string;
311
323
  /**
312
324
  * `chant build --components --generate <lexicon> --pr-loop` (#3183) —
313
325
  * generate the pull-request pipeline: plan on the pull request, apply on
@@ -404,7 +416,7 @@ export interface ParsedArgs {
404
416
  noCache?: boolean;
405
417
  /** `chant workspace graph --composites` (#2662): print each composite instance with the components that can deploy it. */
406
418
  composites?: boolean;
407
- /** `chant workspace graph --intent <path[:start-end]|path#symbol>` (#2651): the region the intent graph is over. Empty with `--record`. */
419
+ /** `chant workspace graph --intent <path[:start-end]|path#symbol|member/node|sha>` (#2651): the region the intent graph is over. Empty with `--record`. */
408
420
  intent?: string;
409
421
  /** `chant workspace graph --intent` and `chant workspace runs` (#3035): follow squash merges to their pull requests' original commits, fetching missing pull request refs. */
410
422
  followSquash?: boolean;
@@ -561,7 +573,7 @@ export interface ParsedArgs {
561
573
  digest?: string;
562
574
  /** `chant components release record --release-plan <file>` (ws-055, #2733) — path to a release plan JSON file, persisted content-addressed to `_plans/<digest>.json` on chant/lifecycle and read back through the read contract (`chant workspace status --json`). The plan's own `digest` field supplies `--digest` when it is omitted, and must match it when both are given. Distinct from `--plan` (#2300, below), the Op gate-approval plan digest. */
563
575
  releasePlanFile?: string;
564
- /** `chant components promote --release <label|digest>` (#4026): deploy under that release, reading the templates its target for `--to` recorded. */
576
+ /** `chant components promote --release <label|digest>` (#4026): deploy under that release, reading the templates its target for `--to` recorded. `chant run --components --release <label|digest>` (#4043): the run is of that release, which must have been cut from the checked-out commit and have a target for `--env`; the release records the run writes name it. */
565
577
  release?: string;
566
578
  /** `chant release cut --build <command>` (#4026): the shell command run in the member's directory before each environment's templates are read, with `CHANT_ENV` set to the environment. */
567
579
  buildCommand?: string;
@@ -660,7 +672,7 @@ export interface ParsedArgs {
660
672
  plan?: string;
661
673
  /** `chant operator log --op <name>` (#2029) — restrict the tick history to one ConvergeOp by name. Omitted, every discovered ConvergeOp's ticks are merged into one timeline. */
662
674
  op?: string;
663
- /** `chant operator log --since <iso>` (#2029) — only entries at or after this ISO-8601 instant. `chant workspace records --since <rev>` (#2673) — the revision to compare the records with. */
675
+ /** `chant operator log --since <iso>` (#2029) — only entries at or after this ISO-8601 instant. `chant workspace records --since <rev>` (#2673) — the revision to compare the records with. `chant workspace metrics --since <date>` (#4039) — the start of the window the metrics count. */
664
676
  since?: string;
665
677
  /** `chant operator log --limit <n>` (#2029) — keep only the newest n entries (still printed oldest-first). */
666
678
  limit?: number;
@@ -37,18 +37,21 @@ export const stdoutLine = (line: string): void => void process.stdout.write(line
37
37
 
38
38
  /**
39
39
  * The run's stream on stdout when `--events-json` was passed, else
40
- * undefined. The run is named by `runId` when the caller has one (so a
40
+ * undefined. Each line goes to stdout, to this host's store and to `also`
41
+ * when given. The run is named by `runId` when the caller has one (so a
41
42
  * release record and the stream name the same run), else by the CI run or a
42
43
  * local id.
43
44
  */
44
45
  export async function openCliRunEvents(
45
46
  args: Pick<ParsedArgs, "eventsJson">,
46
47
  run: { kind: RunEventKind; name: string; runId?: string; cwd?: string },
48
+ also?: (line: string) => void,
47
49
  ): Promise<RunEventStream | undefined> {
48
50
  if (!args.eventsJson) return undefined;
49
51
  const resolved = await resolveRunEventRun(run);
50
52
  // #4042: the stream is also kept on this host, so `chant run events <run>`
51
53
  // replays it. Only a command that was asked for a stream keeps one.
52
54
  const store = await openRunEventStore(run.cwd ?? process.cwd(), resolved.runId);
53
- return openRunEventStream(resolved, teeLines(stdoutLine, store?.write.bind(store))!);
55
+ // `also`: a third copy of each line, such as `--report-url`'s upload (#4007).
56
+ return openRunEventStream(resolved, teeLines(stdoutLine, store?.write.bind(store), also)!);
54
57
  }
@@ -108,6 +108,15 @@ describe("maybeRecordAutoRelease", () => {
108
108
  expect(input).toMatchObject({ runId: "9001", runOrigin: { forge: "github", repo: "acme/app" }, approver: "alice", admission });
109
109
  });
110
110
 
111
+ test("a run under a release records its releaseDigest, and one under none records none (#4043)", async () => {
112
+ appendReleaseRecordMock.mockResolvedValue({ commit: "c".repeat(40), record: { version: 1 } });
113
+ const releaseDigest = `jcs1-sha256:${"c".repeat(64)}`;
114
+ await maybeRecordAutoRelease({ component: "svc", env: "dev", success: true, records: [publishRecord("sha256:abc")], runId: "r", releaseDigest }, { actor: "alice" });
115
+ await maybeRecordAutoRelease({ component: "svc", env: "dev", success: true, records: [publishRecord("sha256:abc")], runId: "r" }, { actor: "alice" });
116
+ expect(appendReleaseRecordMock.mock.calls[0][0]).toMatchObject({ releaseDigest });
117
+ expect(appendReleaseRecordMock.mock.calls[1][0]).not.toHaveProperty("releaseDigest");
118
+ });
119
+
111
120
  test("an ungated deploy records neither", async () => {
112
121
  appendReleaseRecordMock.mockResolvedValue({ commit: "c".repeat(40), record: { version: 1 } });
113
122
  await maybeRecordAutoRelease({ component: "svc", env: "prod", success: true, records: [publishRecord("sha256:abc")], runId: "r" }, { actor: "alice" });
@@ -101,6 +101,8 @@ export interface AutoReleaseRunInfo {
101
101
  profileOverride?: string;
102
102
  /** The deploy's input-side digest, when `digest` is a rendered-content identity (a pinned helm deploy, chant #1242) — recorded as the release record's `inputDigest` so ledger queries can still join on inputs across clusters whose bytes legitimately differ. */
103
103
  inputDigest?: string;
104
+ /** The release the deploy was made under (#4043), recorded as the release record's `releaseDigest`. Set only when the caller named a release; never inferred. */
105
+ releaseDigest?: string;
104
106
  }
105
107
 
106
108
  /** Opt-out + field-override knobs for auto-release recording, threaded from CLI flags/config (#597: "opt-out-able (flag/config), documented default"). */
@@ -191,6 +193,7 @@ export async function maybeRecordAutoRelease(
191
193
  ...(run.admission ? { admission: run.admission } : {}),
192
194
  ...(run.profileOverride ? { profileOverride: run.profileOverride } : {}),
193
195
  ...(run.inputDigest ? { inputDigest: run.inputDigest } : {}),
196
+ ...(run.releaseDigest ? { releaseDigest: run.releaseDigest } : {}),
194
197
  },
195
198
  { cwd: options.cwd },
196
199
  );
@@ -61,6 +61,15 @@ export interface DeployContext {
61
61
  * Absent outside a fan-out that writes a record.
62
62
  */
63
63
  carry?: (member: string, value: unknown) => void;
64
+ /**
65
+ * Set when a step is planned (#4084): the components or stacks whose
66
+ * outputs this step's input reads through `stackOutput()` or
67
+ * `@<component>.publish.*` and which had nothing to read yet, so the input
68
+ * holds `undefined` there. Their values are known only when the run
69
+ * applies them. A `plan` that needs them can say so instead of planning
70
+ * against missing values.
71
+ */
72
+ unresolvedInputs?: string[];
64
73
  }
65
74
 
66
75
  /**
@@ -26,6 +26,7 @@ import {
26
26
  resolveWiring,
27
27
  runComponentDeploy,
28
28
  runInterpretDriver,
29
+ unresolvedWiringSources,
29
30
  type DriverComponent,
30
31
  type DriverStepRecord,
31
32
  type RunProgressEvent,
@@ -1149,3 +1150,26 @@ describe("the release a deploy step sees (#3061, ws-081)", () => {
1149
1150
  expect(deploy.calls[0]!.ctx.release).toEqual({ revision: "r0", version: "sha256:recorded" });
1150
1151
  });
1151
1152
  });
1153
+
1154
+ describe("unresolvedWiringSources (#4084)", () => {
1155
+ it("names each component whose cross-component output has no value yet, sorted and once", () => {
1156
+ const input = {
1157
+ template: "dist/a.json",
1158
+ inputs: {
1159
+ topic: { stackOutput: { stack: "network", name: "TopicArn" } },
1160
+ queue: { stackOutput: { stack: "network", name: "QueueUrl" } },
1161
+ table: { stackOutput: { stack: "cluster-a", name: "TableName" } },
1162
+ image: "@api-image.publish.digest",
1163
+ local: "@Publish.digest",
1164
+ },
1165
+ };
1166
+ expect(unresolvedWiringSources(input, {})).toEqual(["api-image", "cluster-a", "network"]);
1167
+ expect(
1168
+ unresolvedWiringSources(input, {
1169
+ network: { TopicArn: "t", QueueUrl: "q" },
1170
+ "cluster-a": { TableName: "x" },
1171
+ "api-image": { publish: { digest: "sha256:1" } },
1172
+ }),
1173
+ ).toEqual([]);
1174
+ });
1175
+ });
@@ -345,6 +345,42 @@ export function resolveStepInput(
345
345
  return walk(input) as Record<string, unknown>;
346
346
  }
347
347
 
348
+ /**
349
+ * The components (or stacks) whose outputs `input` reads through
350
+ * cross-component wiring, `stackOutput()` or `@<component>.publish.*`, and
351
+ * which have no value for it in `componentOutputs`, sorted and each once
352
+ * (#4084). {@link resolveStepInput} turns each such reference into
353
+ * `undefined`; a plan uses this to say whose outputs it is waiting on.
354
+ */
355
+ export function unresolvedWiringSources(
356
+ input: Record<string, unknown>,
357
+ componentOutputs: Record<string, Record<string, unknown>>,
358
+ ): string[] {
359
+ const found = new Set<string>();
360
+ const walk = (value: unknown): void => {
361
+ if (typeof value === "string") {
362
+ if (PRIOR_STEP_REF.test(value)) return;
363
+ const ref = value.match(COMPONENT_ARTIFACT_REF);
364
+ if (ref && resolvePath(componentOutputs[ref[1]]?.publish, ref[2]) === undefined) found.add(ref[1]);
365
+ return;
366
+ }
367
+ if (Array.isArray(value)) {
368
+ value.forEach(walk);
369
+ return;
370
+ }
371
+ if (value && typeof value === "object") {
372
+ if ("stackOutput" in value) {
373
+ const { stack, name } = (value as { stackOutput: { stack: string; name: string } }).stackOutput;
374
+ if (resolvePath(componentOutputs[stack], name) === undefined) found.add(stack);
375
+ return;
376
+ }
377
+ Object.values(value as Record<string, unknown>).forEach(walk);
378
+ }
379
+ };
380
+ walk(input);
381
+ return [...found].sort();
382
+ }
383
+
348
384
  // ── Step + phase execution ───────────────────────────────────────────────
349
385
 
350
386
  /**