@intentius/chant 0.124.27 → 0.124.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (681) hide show
  1. package/README.md +1 -1
  2. package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
  3. package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
  4. package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  6. package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
  7. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  8. package/dist/__golden__/wave-loop/op-held-on.json +2 -0
  9. package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
  10. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  11. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  12. package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
  13. package/dist/__golden__/wave-loop/op-per-run.json +7 -0
  14. package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
  15. package/dist/__golden__/wave-loop/op-sealed.json +3 -0
  16. package/dist/__golden__/wave-loop/op-spend.json +3 -0
  17. package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
  18. package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
  19. package/dist/build-params.d.ts +34 -1
  20. package/dist/build-params.d.ts.map +1 -1
  21. package/dist/build-params.js +31 -1
  22. package/dist/change-set-graph.d.ts +63 -0
  23. package/dist/change-set-graph.d.ts.map +1 -0
  24. package/dist/change-set-graph.js +130 -0
  25. package/dist/change-set.d.ts +62 -3
  26. package/dist/change-set.d.ts.map +1 -1
  27. package/dist/change-set.js +36 -4
  28. package/dist/chant-version.d.ts +9 -0
  29. package/dist/chant-version.d.ts.map +1 -0
  30. package/dist/chant-version.js +16 -0
  31. package/dist/cli/build-params-cli.d.ts +7 -1
  32. package/dist/cli/build-params-cli.d.ts.map +1 -1
  33. package/dist/cli/build-params-cli.js +17 -8
  34. package/dist/cli/commands/build.d.ts +8 -0
  35. package/dist/cli/commands/build.d.ts.map +1 -1
  36. package/dist/cli/commands/build.js +15 -11
  37. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  38. package/dist/cli/commands/check-lexicon-docs.js +30 -2
  39. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  40. package/dist/cli/commands/check-lexicon.js +13 -2
  41. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
  42. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  43. package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
  44. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  45. package/dist/cli/commands/init-lexicon.js +17 -8
  46. package/dist/cli/handlers/approve.d.ts.map +1 -1
  47. package/dist/cli/handlers/approve.js +64 -4
  48. package/dist/cli/handlers/build.d.ts.map +1 -1
  49. package/dist/cli/handlers/build.js +43 -4
  50. package/dist/cli/handlers/change-set.d.ts +10 -0
  51. package/dist/cli/handlers/change-set.d.ts.map +1 -1
  52. package/dist/cli/handlers/change-set.js +51 -1
  53. package/dist/cli/handlers/fan-out.d.ts +10 -1
  54. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  55. package/dist/cli/handlers/fan-out.js +54 -6
  56. package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
  57. package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
  58. package/dist/cli/handlers/lifecycle-gc.js +143 -0
  59. package/dist/cli/handlers/pr.d.ts +16 -2
  60. package/dist/cli/handlers/pr.d.ts.map +1 -1
  61. package/dist/cli/handlers/pr.js +269 -37
  62. package/dist/cli/handlers/promote.d.ts +7 -0
  63. package/dist/cli/handlers/promote.d.ts.map +1 -1
  64. package/dist/cli/handlers/promote.js +440 -28
  65. package/dist/cli/handlers/release.d.ts +91 -0
  66. package/dist/cli/handlers/release.d.ts.map +1 -0
  67. package/dist/cli/handlers/release.js +589 -0
  68. package/dist/cli/handlers/run.d.ts +38 -2
  69. package/dist/cli/handlers/run.d.ts.map +1 -1
  70. package/dist/cli/handlers/run.js +234 -16
  71. package/dist/cli/main.d.ts.map +1 -1
  72. package/dist/cli/main.js +143 -17
  73. package/dist/cli/mcp/workspace-tools.js +1 -1
  74. package/dist/cli/parse-args.d.ts.map +1 -1
  75. package/dist/cli/parse-args.js +73 -5
  76. package/dist/cli/registry.d.ts +43 -5
  77. package/dist/cli/registry.d.ts.map +1 -1
  78. package/dist/cli/run-events.d.ts +30 -0
  79. package/dist/cli/run-events.d.ts.map +1 -0
  80. package/dist/cli/run-events.js +49 -0
  81. package/dist/cli/version.d.ts +3 -5
  82. package/dist/cli/version.d.ts.map +1 -1
  83. package/dist/cli/version.js +3 -13
  84. package/dist/codegen/docs-in-site.d.ts +71 -0
  85. package/dist/codegen/docs-in-site.d.ts.map +1 -0
  86. package/dist/codegen/docs-in-site.js +177 -0
  87. package/dist/codegen/docs-types.d.ts +14 -1
  88. package/dist/codegen/docs-types.d.ts.map +1 -1
  89. package/dist/codegen/docs.d.ts +18 -1
  90. package/dist/codegen/docs.d.ts.map +1 -1
  91. package/dist/codegen/docs.js +40 -7
  92. package/dist/codegen/rehype-base-url.mjs +15 -9
  93. package/dist/components/auto-release.d.ts +6 -1
  94. package/dist/components/auto-release.d.ts.map +1 -1
  95. package/dist/components/auto-release.js +3 -0
  96. package/dist/components/capability.d.ts +9 -0
  97. package/dist/components/capability.d.ts.map +1 -1
  98. package/dist/components/cli-support.d.ts +6 -0
  99. package/dist/components/cli-support.d.ts.map +1 -1
  100. package/dist/components/cli-support.js +10 -9
  101. package/dist/components/driver.d.ts +50 -9
  102. package/dist/components/driver.d.ts.map +1 -1
  103. package/dist/components/driver.js +116 -54
  104. package/dist/components/fan-out-inputs.d.ts +69 -0
  105. package/dist/components/fan-out-inputs.d.ts.map +1 -0
  106. package/dist/components/fan-out-inputs.js +169 -0
  107. package/dist/components/fan-out-record.d.ts +13 -0
  108. package/dist/components/fan-out-record.d.ts.map +1 -1
  109. package/dist/components/fan-out-record.js +16 -0
  110. package/dist/components/fan-out-run.d.ts +7 -0
  111. package/dist/components/fan-out-run.d.ts.map +1 -1
  112. package/dist/components/fan-out-run.js +85 -26
  113. package/dist/components/fan-out.d.ts +4 -0
  114. package/dist/components/fan-out.d.ts.map +1 -1
  115. package/dist/components/pr-pipeline.d.ts +25 -0
  116. package/dist/components/pr-pipeline.d.ts.map +1 -1
  117. package/dist/components/pr-pipeline.js +22 -4
  118. package/dist/components/pr-run.d.ts +11 -0
  119. package/dist/components/pr-run.d.ts.map +1 -1
  120. package/dist/components/pr-run.js +20 -1
  121. package/dist/components/promote-order.d.ts +38 -0
  122. package/dist/components/promote-order.d.ts.map +1 -0
  123. package/dist/components/promote-order.js +62 -0
  124. package/dist/components/promote.d.ts +38 -2
  125. package/dist/components/promote.d.ts.map +1 -1
  126. package/dist/components/promote.js +77 -2
  127. package/dist/components/release-compile.d.ts +142 -0
  128. package/dist/components/release-compile.d.ts.map +1 -0
  129. package/dist/components/release-compile.js +382 -0
  130. package/dist/components/release-gate.d.ts +135 -0
  131. package/dist/components/release-gate.d.ts.map +1 -0
  132. package/dist/components/release-gate.js +288 -0
  133. package/dist/components/release-promote.d.ts +96 -0
  134. package/dist/components/release-promote.d.ts.map +1 -0
  135. package/dist/components/release-promote.js +204 -0
  136. package/dist/components/run-progress.d.ts +7 -0
  137. package/dist/components/run-progress.d.ts.map +1 -1
  138. package/dist/components/wave-plan.d.ts +8 -1
  139. package/dist/components/wave-plan.d.ts.map +1 -1
  140. package/dist/components/wave-plan.js +11 -3
  141. package/dist/config.d.ts +45 -0
  142. package/dist/config.d.ts.map +1 -1
  143. package/dist/config.js +3 -0
  144. package/dist/flag-params.d.ts +54 -0
  145. package/dist/flag-params.d.ts.map +1 -0
  146. package/dist/flag-params.js +130 -0
  147. package/dist/index.d.ts +1 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +2 -0
  150. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  151. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  152. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  153. package/dist/lifecycle/change-set-store.d.ts +68 -0
  154. package/dist/lifecycle/change-set-store.d.ts.map +1 -0
  155. package/dist/lifecycle/change-set-store.js +97 -0
  156. package/dist/lifecycle/commit-sha.d.ts +13 -0
  157. package/dist/lifecycle/commit-sha.d.ts.map +1 -0
  158. package/dist/lifecycle/commit-sha.js +21 -0
  159. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  160. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  161. package/dist/lifecycle/delivery-metrics.js +145 -0
  162. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  163. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  164. package/dist/lifecycle/deploy-outcome.js +101 -0
  165. package/dist/lifecycle/gate-change-set.d.ts +78 -0
  166. package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
  167. package/dist/lifecycle/gate-change-set.js +138 -0
  168. package/dist/lifecycle/gate-ledger.d.ts +51 -4
  169. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  170. package/dist/lifecycle/gate-ledger.js +18 -0
  171. package/dist/lifecycle/git.d.ts +30 -1
  172. package/dist/lifecycle/git.d.ts.map +1 -1
  173. package/dist/lifecycle/git.js +31 -5
  174. package/dist/lifecycle/index.d.ts +4 -0
  175. package/dist/lifecycle/index.d.ts.map +1 -1
  176. package/dist/lifecycle/index.js +4 -0
  177. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  178. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  179. package/dist/lifecycle/mixed-environment.js +154 -0
  180. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  181. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  182. package/dist/lifecycle/publish-attachments.js +179 -0
  183. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  184. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  185. package/dist/lifecycle/publish-credentials.js +69 -0
  186. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  187. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  188. package/dist/lifecycle/publish-declaration.js +83 -0
  189. package/dist/lifecycle/publish-forge.d.ts +53 -0
  190. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  191. package/dist/lifecycle/publish-forge.js +298 -0
  192. package/dist/lifecycle/publish-npm.d.ts +61 -0
  193. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  194. package/dist/lifecycle/publish-npm.js +242 -0
  195. package/dist/lifecycle/publish-oci.d.ts +64 -0
  196. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  197. package/dist/lifecycle/publish-oci.js +383 -0
  198. package/dist/lifecycle/publish-record.d.ts +66 -0
  199. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  200. package/dist/lifecycle/publish-record.js +84 -0
  201. package/dist/lifecycle/publish-targets.d.ts +45 -0
  202. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  203. package/dist/lifecycle/publish-targets.js +85 -0
  204. package/dist/lifecycle/release-cut.d.ts +154 -0
  205. package/dist/lifecycle/release-cut.d.ts.map +1 -0
  206. package/dist/lifecycle/release-cut.js +366 -0
  207. package/dist/lifecycle/release-join.d.ts +95 -0
  208. package/dist/lifecycle/release-join.d.ts.map +1 -0
  209. package/dist/lifecycle/release-join.js +139 -0
  210. package/dist/lifecycle/release-ledger.d.ts +54 -0
  211. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  212. package/dist/lifecycle/release-ledger.js +53 -20
  213. package/dist/lifecycle/release-publish.d.ts +312 -0
  214. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  215. package/dist/lifecycle/release-publish.js +335 -0
  216. package/dist/lifecycle/release-revocation.d.ts +80 -0
  217. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  218. package/dist/lifecycle/release-revocation.js +123 -0
  219. package/dist/lifecycle/release-store.d.ts +200 -0
  220. package/dist/lifecycle/release-store.d.ts.map +1 -0
  221. package/dist/lifecycle/release-store.js +320 -0
  222. package/dist/lifecycle/release-verify.d.ts +61 -0
  223. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  224. package/dist/lifecycle/release-verify.js +92 -0
  225. package/dist/lifecycle/release-workstation.d.ts +91 -0
  226. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  227. package/dist/lifecycle/release-workstation.js +180 -0
  228. package/dist/lifecycle/retention-policy.d.ts +45 -0
  229. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  230. package/dist/lifecycle/retention-policy.js +43 -0
  231. package/dist/lifecycle/retention.d.ts +185 -0
  232. package/dist/lifecycle/retention.d.ts.map +1 -0
  233. package/dist/lifecycle/retention.js +400 -0
  234. package/dist/lifecycle/run-ledger.d.ts +4 -0
  235. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  236. package/dist/lifecycle/run-ledger.js +2 -0
  237. package/dist/op/activities/change-coverage.d.ts.map +1 -1
  238. package/dist/op/activities/change-coverage.js +4 -2
  239. package/dist/op/activities/source-release.d.ts.map +1 -1
  240. package/dist/op/activities/source-release.js +7 -3
  241. package/dist/op/activities/source-rollback.d.ts.map +1 -1
  242. package/dist/op/activities/source-rollback.js +7 -3
  243. package/dist/op/gate-name.d.ts +11 -2
  244. package/dist/op/gate-name.d.ts.map +1 -1
  245. package/dist/op/gate-name.js +12 -3
  246. package/dist/op/gate-run-locator.d.ts +22 -0
  247. package/dist/op/gate-run-locator.d.ts.map +1 -1
  248. package/dist/op/gate-run-locator.js +31 -0
  249. package/dist/op/gate.d.ts +27 -3
  250. package/dist/op/gate.d.ts.map +1 -1
  251. package/dist/op/gate.js +57 -3
  252. package/dist/op/local-executor.d.ts +36 -1
  253. package/dist/op/local-executor.d.ts.map +1 -1
  254. package/dist/op/local-executor.js +158 -52
  255. package/dist/op/op-waves-run.d.ts.map +1 -1
  256. package/dist/op/op-waves-run.js +4 -0
  257. package/dist/op/run-live.d.ts +2 -0
  258. package/dist/op/run-live.d.ts.map +1 -1
  259. package/dist/op/run-live.js +1 -1
  260. package/dist/op/runtime.d.ts +46 -0
  261. package/dist/op/runtime.d.ts.map +1 -1
  262. package/dist/op/runtimes/local.d.ts.map +1 -1
  263. package/dist/op/runtimes/local.js +8 -0
  264. package/dist/plan-summary.d.ts.map +1 -1
  265. package/dist/plan-summary.js +8 -2
  266. package/dist/point-decision.d.ts +2 -0
  267. package/dist/point-decision.d.ts.map +1 -1
  268. package/dist/point-decision.js +9 -2
  269. package/dist/pr-loop.d.ts +7 -0
  270. package/dist/pr-loop.d.ts.map +1 -1
  271. package/dist/pr-loop.js +23 -4
  272. package/dist/pr-report-url.d.ts +82 -0
  273. package/dist/pr-report-url.d.ts.map +1 -0
  274. package/dist/pr-report-url.js +174 -0
  275. package/dist/provenance.d.ts +2 -2
  276. package/dist/provenance.d.ts.map +1 -1
  277. package/dist/run-event-store.d.ts +104 -0
  278. package/dist/run-event-store.d.ts.map +1 -0
  279. package/dist/run-event-store.js +251 -0
  280. package/dist/run-event-stream.d.ts +133 -0
  281. package/dist/run-event-stream.d.ts.map +1 -0
  282. package/dist/run-event-stream.js +242 -0
  283. package/dist/run-event.d.ts +285 -0
  284. package/dist/run-event.d.ts.map +1 -0
  285. package/dist/run-event.js +209 -0
  286. package/dist/wave-runtime.mjs +262 -226
  287. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  288. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  289. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  290. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  291. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  292. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  293. package/dist/workspace/change-set-show.d.ts +56 -0
  294. package/dist/workspace/change-set-show.d.ts.map +1 -0
  295. package/dist/workspace/change-set-show.js +54 -0
  296. package/dist/workspace/change-set-show.schema.json +102 -0
  297. package/dist/workspace/change-set.schema.json +32 -1
  298. package/dist/workspace/changes-cli.d.ts.map +1 -1
  299. package/dist/workspace/changes-cli.js +10 -1
  300. package/dist/workspace/changes.d.ts +37 -0
  301. package/dist/workspace/changes.d.ts.map +1 -1
  302. package/dist/workspace/changes.js +49 -2
  303. package/dist/workspace/changes.schema.json +135 -0
  304. package/dist/workspace/check.schema.json +4 -2
  305. package/dist/workspace/checks/records.d.ts +9 -1
  306. package/dist/workspace/checks/records.d.ts.map +1 -1
  307. package/dist/workspace/checks/records.js +35 -1
  308. package/dist/workspace/checks.d.ts +1 -0
  309. package/dist/workspace/checks.d.ts.map +1 -1
  310. package/dist/workspace/checks.js +2 -1
  311. package/dist/workspace/ci-green-cli.d.ts +19 -4
  312. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  313. package/dist/workspace/ci-green-cli.js +75 -12
  314. package/dist/workspace/ci-green-forge.d.ts +20 -3
  315. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  316. package/dist/workspace/ci-green-forge.js +6 -4
  317. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  318. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  319. package/dist/workspace/ci-green-workflow.js +6 -0
  320. package/dist/workspace/ci-green.d.ts +281 -13
  321. package/dist/workspace/ci-green.d.ts.map +1 -1
  322. package/dist/workspace/ci-green.js +456 -38
  323. package/dist/workspace/ci-last-green.schema.json +32 -2
  324. package/dist/workspace/ci-list.schema.json +525 -0
  325. package/dist/workspace/commit-links.d.ts +85 -0
  326. package/dist/workspace/commit-links.d.ts.map +1 -0
  327. package/dist/workspace/commit-links.js +163 -0
  328. package/dist/workspace/declaration.d.ts +35 -0
  329. package/dist/workspace/declaration.d.ts.map +1 -1
  330. package/dist/workspace/declaration.js +27 -1
  331. package/dist/workspace/declaration.schema.json +76 -0
  332. package/dist/workspace/declared-kinds.d.ts +16 -0
  333. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  334. package/dist/workspace/declared-kinds.js +25 -0
  335. package/dist/workspace/delivery-metrics.d.ts +91 -0
  336. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  337. package/dist/workspace/delivery-metrics.js +217 -0
  338. package/dist/workspace/delivery-metrics.schema.json +519 -0
  339. package/dist/workspace/flags-read.d.ts +37 -0
  340. package/dist/workspace/flags-read.d.ts.map +1 -0
  341. package/dist/workspace/flags-read.js +103 -0
  342. package/dist/workspace/flags.d.ts +100 -0
  343. package/dist/workspace/flags.d.ts.map +1 -0
  344. package/dist/workspace/flags.js +98 -0
  345. package/dist/workspace/graph-cli.js +1 -1
  346. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  347. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  348. package/dist/workspace/identity-gate-rule.js +72 -7
  349. package/dist/workspace/identity.d.ts +12 -0
  350. package/dist/workspace/identity.d.ts.map +1 -1
  351. package/dist/workspace/identity.js +13 -1
  352. package/dist/workspace/intent-cli.d.ts +11 -2
  353. package/dist/workspace/intent-cli.d.ts.map +1 -1
  354. package/dist/workspace/intent-cli.js +54 -5
  355. package/dist/workspace/intent-trace.d.ts +219 -0
  356. package/dist/workspace/intent-trace.d.ts.map +1 -0
  357. package/dist/workspace/intent-trace.js +314 -0
  358. package/dist/workspace/intent.d.ts +36 -7
  359. package/dist/workspace/intent.d.ts.map +1 -1
  360. package/dist/workspace/intent.js +192 -22
  361. package/dist/workspace/intent.schema.json +702 -6
  362. package/dist/workspace/ls.d.ts +4 -0
  363. package/dist/workspace/ls.d.ts.map +1 -1
  364. package/dist/workspace/ls.js +2 -0
  365. package/dist/workspace/ls.schema.json +3 -1
  366. package/dist/workspace/member-pipeline.d.ts +6 -0
  367. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  368. package/dist/workspace/member-pipeline.js +6 -0
  369. package/dist/workspace/overlap.d.ts +146 -0
  370. package/dist/workspace/overlap.d.ts.map +1 -0
  371. package/dist/workspace/overlap.js +276 -0
  372. package/dist/workspace/promotion-order.d.ts +41 -0
  373. package/dist/workspace/promotion-order.d.ts.map +1 -0
  374. package/dist/workspace/promotion-order.js +95 -0
  375. package/dist/workspace/reason-codes.d.ts +4 -0
  376. package/dist/workspace/reason-codes.d.ts.map +1 -1
  377. package/dist/workspace/reason-codes.js +7 -1
  378. package/dist/workspace/records.d.ts +15 -2
  379. package/dist/workspace/records.d.ts.map +1 -1
  380. package/dist/workspace/records.js +32 -0
  381. package/dist/workspace/records.schema.json +21 -2
  382. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  383. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  384. package/dist/workspace/releases.d.ts +81 -0
  385. package/dist/workspace/releases.d.ts.map +1 -0
  386. package/dist/workspace/releases.js +103 -0
  387. package/dist/workspace/releases.schema.json +513 -0
  388. package/dist/workspace/run-event.schema.json +295 -0
  389. package/dist/workspace/status-gates.d.ts +34 -1
  390. package/dist/workspace/status-gates.d.ts.map +1 -1
  391. package/dist/workspace/status-gates.js +31 -2
  392. package/dist/workspace/status-stewards.d.ts +4 -0
  393. package/dist/workspace/status-stewards.d.ts.map +1 -1
  394. package/dist/workspace/status-stewards.js +2 -0
  395. package/dist/workspace/status.d.ts +117 -0
  396. package/dist/workspace/status.d.ts.map +1 -1
  397. package/dist/workspace/status.js +218 -4
  398. package/dist/workspace/status.schema.json +268 -2
  399. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  400. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  401. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  402. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  403. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  404. package/dist/workspace/trust/op-run-statement.js +143 -0
  405. package/dist/workspace/trust/rotation.d.ts +18 -2
  406. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  407. package/dist/workspace/trust/rotation.js +65 -16
  408. package/dist/workspace/trust/seal.d.ts +11 -0
  409. package/dist/workspace/trust/seal.d.ts.map +1 -1
  410. package/dist/workspace/trust/seal.js +9 -0
  411. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  412. package/dist/workspace/trust/ssh-commit.js +10 -10
  413. package/dist/workspace/wip.d.ts.map +1 -1
  414. package/dist/workspace/wip.js +11 -3
  415. package/package.json +1 -1
  416. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  417. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  418. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  419. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  420. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  421. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  422. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  423. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  424. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  425. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  426. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  427. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  428. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  429. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  430. package/src/__golden__/wave-loop/op-spend.json +3 -0
  431. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  432. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  433. package/src/build-params.test.ts +46 -0
  434. package/src/build-params.ts +55 -1
  435. package/src/change-set-graph.test.ts +72 -0
  436. package/src/change-set-graph.ts +141 -0
  437. package/src/change-set.ts +87 -6
  438. package/src/chant-version.ts +16 -0
  439. package/src/cli/build-params-cli.ts +22 -9
  440. package/src/cli/commands/build.ts +25 -11
  441. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  442. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  443. package/src/cli/commands/check-lexicon.ts +14 -2
  444. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  445. package/src/cli/commands/init-lexicon.test.ts +25 -0
  446. package/src/cli/commands/init-lexicon.ts +18 -8
  447. package/src/cli/handlers/approve-release.test.ts +84 -0
  448. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  449. package/src/cli/handlers/approve.ts +65 -2
  450. package/src/cli/handlers/build.test.ts +67 -0
  451. package/src/cli/handlers/build.ts +46 -4
  452. package/src/cli/handlers/change-set.ts +49 -1
  453. package/src/cli/handlers/fan-out.test.ts +228 -1
  454. package/src/cli/handlers/fan-out.ts +53 -6
  455. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  456. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  457. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  458. package/src/cli/handlers/operator.test.ts +37 -0
  459. package/src/cli/handlers/pr.test.ts +341 -0
  460. package/src/cli/handlers/pr.ts +292 -47
  461. package/src/cli/handlers/promote.test.ts +669 -7
  462. package/src/cli/handlers/promote.ts +464 -29
  463. package/src/cli/handlers/release-implicit.e2e.test.ts +161 -0
  464. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  465. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  466. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  467. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  468. package/src/cli/handlers/release.e2e.test.ts +199 -0
  469. package/src/cli/handlers/release.ts +593 -0
  470. package/src/cli/handlers/run-auto-release.test.ts +80 -0
  471. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  472. package/src/cli/handlers/run-release.test.ts +55 -0
  473. package/src/cli/handlers/run.test.ts +30 -0
  474. package/src/cli/handlers/run.ts +243 -18
  475. package/src/cli/main.test.ts +8 -0
  476. package/src/cli/main.ts +143 -17
  477. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  478. package/src/cli/mcp/workspace-tools.ts +1 -1
  479. package/src/cli/parse-args.ts +56 -5
  480. package/src/cli/registry.ts +43 -5
  481. package/src/cli/run-events.ts +57 -0
  482. package/src/cli/version.ts +3 -13
  483. package/src/codegen/docs-in-site.test.ts +166 -0
  484. package/src/codegen/docs-in-site.ts +182 -0
  485. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  486. package/src/codegen/docs-types.ts +14 -1
  487. package/src/codegen/docs.ts +60 -7
  488. package/src/codegen/rehype-base-url.d.mts +3 -1
  489. package/src/codegen/rehype-base-url.mjs +15 -9
  490. package/src/codegen/rehype-base-url.test.ts +34 -0
  491. package/src/components/auto-release.test.ts +30 -0
  492. package/src/components/auto-release.ts +9 -1
  493. package/src/components/capability.ts +9 -0
  494. package/src/components/cli-support.test.ts +82 -0
  495. package/src/components/cli-support.ts +16 -9
  496. package/src/components/driver.test.ts +24 -0
  497. package/src/components/driver.ts +165 -56
  498. package/src/components/fan-out-inputs.test.ts +85 -0
  499. package/src/components/fan-out-inputs.ts +192 -0
  500. package/src/components/fan-out-record.test.ts +10 -0
  501. package/src/components/fan-out-record.ts +19 -0
  502. package/src/components/fan-out-run.ts +104 -35
  503. package/src/components/fan-out.ts +4 -0
  504. package/src/components/pr-pipeline.ts +35 -4
  505. package/src/components/pr-run.ts +30 -1
  506. package/src/components/promote-order.test.ts +90 -0
  507. package/src/components/promote-order.ts +75 -0
  508. package/src/components/promote.test.ts +10 -1
  509. package/src/components/promote.ts +94 -3
  510. package/src/components/release-compile.test.ts +147 -0
  511. package/src/components/release-compile.ts +459 -0
  512. package/src/components/release-gate.test.ts +215 -0
  513. package/src/components/release-gate.ts +355 -0
  514. package/src/components/release-promote.test.ts +159 -0
  515. package/src/components/release-promote.ts +232 -0
  516. package/src/components/run-progress.ts +7 -0
  517. package/src/components/wave-plan.ts +16 -3
  518. package/src/config.ts +15 -1
  519. package/src/flag-params.test.ts +144 -0
  520. package/src/flag-params.ts +134 -0
  521. package/src/index.ts +2 -0
  522. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  523. package/src/lifecycle/change-set-store.test.ts +217 -0
  524. package/src/lifecycle/change-set-store.ts +113 -0
  525. package/src/lifecycle/commit-sha.ts +23 -0
  526. package/src/lifecycle/delivery-metrics.ts +263 -0
  527. package/src/lifecycle/deploy-outcome.ts +147 -0
  528. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  529. package/src/lifecycle/gate-change-set.ts +164 -0
  530. package/src/lifecycle/gate-ledger.test.ts +33 -0
  531. package/src/lifecycle/gate-ledger.ts +68 -3
  532. package/src/lifecycle/git.ts +61 -8
  533. package/src/lifecycle/index.ts +4 -0
  534. package/src/lifecycle/mixed-environment.test.ts +97 -0
  535. package/src/lifecycle/mixed-environment.ts +204 -0
  536. package/src/lifecycle/publish-attachments.ts +196 -0
  537. package/src/lifecycle/publish-credentials.ts +84 -0
  538. package/src/lifecycle/publish-declaration.ts +95 -0
  539. package/src/lifecycle/publish-forge.ts +325 -0
  540. package/src/lifecycle/publish-npm.ts +259 -0
  541. package/src/lifecycle/publish-oci.ts +396 -0
  542. package/src/lifecycle/publish-record.ts +113 -0
  543. package/src/lifecycle/publish-resume.test.ts +168 -0
  544. package/src/lifecycle/publish-targets.test.ts +494 -0
  545. package/src/lifecycle/publish-targets.ts +100 -0
  546. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  547. package/src/lifecycle/release-cut.test.ts +483 -0
  548. package/src/lifecycle/release-cut.ts +503 -0
  549. package/src/lifecycle/release-flag.test.ts +112 -0
  550. package/src/lifecycle/release-join.test.ts +68 -0
  551. package/src/lifecycle/release-join.ts +182 -0
  552. package/src/lifecycle/release-ledger.test.ts +14 -0
  553. package/src/lifecycle/release-ledger.ts +85 -17
  554. package/src/lifecycle/release-publish.test.ts +225 -0
  555. package/src/lifecycle/release-publish.ts +568 -0
  556. package/src/lifecycle/release-revocation.ts +147 -0
  557. package/src/lifecycle/release-store.ts +434 -0
  558. package/src/lifecycle/release-verify.test.ts +110 -0
  559. package/src/lifecycle/release-verify.ts +120 -0
  560. package/src/lifecycle/release-workstation.test.ts +204 -0
  561. package/src/lifecycle/release-workstation.ts +216 -0
  562. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  563. package/src/lifecycle/retention-policy.ts +63 -0
  564. package/src/lifecycle/retention.test.ts +363 -0
  565. package/src/lifecycle/retention.ts +508 -0
  566. package/src/lifecycle/run-ledger.test.ts +10 -0
  567. package/src/lifecycle/run-ledger.ts +6 -0
  568. package/src/op/activities/change-coverage.ts +4 -1
  569. package/src/op/activities/source-release.ts +8 -3
  570. package/src/op/activities/source-rollback.ts +8 -4
  571. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  572. package/src/op/gate-name.ts +13 -3
  573. package/src/op/gate-release.test.ts +126 -0
  574. package/src/op/gate-run-locator.test.ts +110 -0
  575. package/src/op/gate-run-locator.ts +35 -0
  576. package/src/op/gate.ts +88 -6
  577. package/src/op/local-executor.ts +207 -52
  578. package/src/op/op-waves-run.ts +4 -0
  579. package/src/op/run-events.test.ts +166 -0
  580. package/src/op/run-live.ts +1 -1
  581. package/src/op/runtime.ts +39 -0
  582. package/src/op/runtimes/local.test.ts +16 -0
  583. package/src/op/runtimes/local.ts +9 -0
  584. package/src/plan-summary.test.ts +20 -0
  585. package/src/plan-summary.ts +8 -1
  586. package/src/point-decision.ts +12 -2
  587. package/src/pr-loop.ts +25 -4
  588. package/src/pr-report-url.test.ts +131 -0
  589. package/src/pr-report-url.ts +209 -0
  590. package/src/provenance.ts +2 -2
  591. package/src/run-event-store.test.ts +144 -0
  592. package/src/run-event-store.ts +273 -0
  593. package/src/run-event-stream.test.ts +175 -0
  594. package/src/run-event-stream.ts +309 -0
  595. package/src/run-event.test.ts +277 -0
  596. package/src/run-event.ts +471 -0
  597. package/src/wave-loop-golden.test.ts +9 -0
  598. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  599. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  600. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  601. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  602. package/src/workspace/ancestry-index.test.ts +93 -0
  603. package/src/workspace/change-set-show.schema.json +102 -0
  604. package/src/workspace/change-set-show.ts +72 -0
  605. package/src/workspace/change-set.schema.json +32 -1
  606. package/src/workspace/changes-cli.ts +10 -1
  607. package/src/workspace/changes.schema.json +135 -0
  608. package/src/workspace/changes.test.ts +1 -1
  609. package/src/workspace/changes.ts +84 -2
  610. package/src/workspace/check.schema.json +4 -2
  611. package/src/workspace/checks/records.ts +35 -1
  612. package/src/workspace/checks.ts +2 -1
  613. package/src/workspace/ci-green-cli.ts +75 -13
  614. package/src/workspace/ci-green-forge.ts +24 -4
  615. package/src/workspace/ci-green-runs.test.ts +242 -0
  616. package/src/workspace/ci-green-signed.test.ts +302 -0
  617. package/src/workspace/ci-green-workflow.test.ts +16 -0
  618. package/src/workspace/ci-green-workflow.ts +12 -0
  619. package/src/workspace/ci-green.test.ts +131 -2
  620. package/src/workspace/ci-green.ts +635 -42
  621. package/src/workspace/ci-last-green.schema.json +32 -2
  622. package/src/workspace/ci-list.schema.json +525 -0
  623. package/src/workspace/commit-links.test.ts +279 -0
  624. package/src/workspace/commit-links.ts +183 -0
  625. package/src/workspace/declaration.schema.json +76 -0
  626. package/src/workspace/declaration.test.ts +28 -0
  627. package/src/workspace/declaration.ts +60 -2
  628. package/src/workspace/declared-kinds.ts +40 -1
  629. package/src/workspace/delivery-metrics.schema.json +519 -0
  630. package/src/workspace/delivery-metrics.test.ts +236 -0
  631. package/src/workspace/delivery-metrics.ts +286 -0
  632. package/src/workspace/flags-read.ts +121 -0
  633. package/src/workspace/flags.test.ts +141 -0
  634. package/src/workspace/flags.ts +157 -0
  635. package/src/workspace/graph-cli.ts +1 -1
  636. package/src/workspace/identity-gate-rule.ts +91 -7
  637. package/src/workspace/identity.ts +14 -1
  638. package/src/workspace/intent-cli.ts +46 -5
  639. package/src/workspace/intent-trace.test.ts +173 -0
  640. package/src/workspace/intent-trace.ts +484 -0
  641. package/src/workspace/intent.schema.json +702 -6
  642. package/src/workspace/intent.ts +196 -26
  643. package/src/workspace/ls.schema.json +3 -1
  644. package/src/workspace/ls.ts +6 -0
  645. package/src/workspace/member-pipeline.test.ts +17 -0
  646. package/src/workspace/member-pipeline.ts +9 -0
  647. package/src/workspace/older-records.test.ts +412 -0
  648. package/src/workspace/overlap.test.ts +165 -0
  649. package/src/workspace/overlap.ts +340 -0
  650. package/src/workspace/promotion-order.ts +97 -0
  651. package/src/workspace/read-contract.e2e.test.ts +5 -1
  652. package/src/workspace/reason-codes.test.ts +7 -1
  653. package/src/workspace/reason-codes.ts +7 -1
  654. package/src/workspace/record-assets.test.ts +2 -1
  655. package/src/workspace/records-contract.e2e.test.ts +2 -1
  656. package/src/workspace/records.schema.json +21 -2
  657. package/src/workspace/records.ts +36 -2
  658. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  659. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  660. package/src/workspace/releases.schema.json +513 -0
  661. package/src/workspace/releases.test.ts +108 -0
  662. package/src/workspace/releases.ts +133 -0
  663. package/src/workspace/run-event.schema.json +295 -0
  664. package/src/workspace/status-contract.test.ts +81 -1
  665. package/src/workspace/status-gates.ts +64 -1
  666. package/src/workspace/status-mixed.test.ts +152 -0
  667. package/src/workspace/status-revoked.test.ts +153 -0
  668. package/src/workspace/status-stewards.ts +6 -0
  669. package/src/workspace/status.schema.json +268 -2
  670. package/src/workspace/status.ts +334 -4
  671. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  672. package/src/workspace/trust/op-run-statement.ts +148 -0
  673. package/src/workspace/trust/rotation.ts +61 -14
  674. package/src/workspace/trust/seal.ts +10 -0
  675. package/src/workspace/trust/ssh-commit.ts +10 -9
  676. package/src/workspace/wip.ts +11 -2
  677. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  678. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  679. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  680. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  681. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The `releases` read (#4026, ws-104): releases with their targets, cuts and
3
+ * the environments that run them, held to `releases.schema.json`.
4
+ */
5
+
6
+ import { describe, test, expect } from "vitest";
7
+ import { withTestDir } from "@intentius/chant-test-utils";
8
+ import { spawnSync } from "node:child_process";
9
+ import { writeFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { contract } from "./__fixtures__/contract-repo";
12
+ import releasesSchema from "./releases.schema.json";
13
+ import { releasesDocument, RELEASES_SCHEMA_ID } from "./releases";
14
+ import { appendCut, makeRelease, persistRelease } from "../lifecycle/release-store";
15
+ import { appendReleaseRecord } from "../lifecycle/release-ledger";
16
+ import { writeLedgerFiles } from "../lifecycle/git";
17
+
18
+ const { expectValid } = contract(releasesSchema);
19
+
20
+ function initRepo(dir: string): void {
21
+ for (const args of [["init", "-q", "-b", "main"], ["config", "user.email", "t@chant.dev"], ["config", "user.name", "T"]]) spawnSync("git", args, { cwd: dir });
22
+ writeFileSync(join(dir, "README.md"), "x\n");
23
+ spawnSync("git", ["add", "."], { cwd: dir });
24
+ spawnSync("git", ["commit", "-q", "-m", "init"], { cwd: dir });
25
+ }
26
+
27
+ const release = (commit: string) =>
28
+ makeRelease({
29
+ member: null,
30
+ commit,
31
+ artifactDigests: [{ component: "api", kind: "image", path: "api.tar", digest: `sha256:${"a".repeat(64)}` }],
32
+ targets: [
33
+ { env: "staging", templates: [{ component: "api", path: "t.json", digest: `sha256:${"b".repeat(64)}` }], params: [{ name: "tier", value: "small", source: "default" }] },
34
+ { env: "prod", templates: [{ component: "api", path: "t.json", digest: `sha256:${"c".repeat(64)}` }], params: [{ name: "tier", value: "large", source: "default" }] },
35
+ ],
36
+ });
37
+
38
+ const deploy = (env: string, component: string, releaseDigest?: string, timestamp = "2026-10-16T12:00:00.000Z") =>
39
+ appendReleaseRecord({ component, env, digest: `sha256:${"a".repeat(64)}`, gitSha: "1".repeat(40), runId: "r", timestamp, actor: "ci", ...(releaseDigest ? { releaseDigest } : {}) }, { cwd: dirOf.current });
40
+ const dirOf = { current: "" };
41
+
42
+ describe("releasesDocument", () => {
43
+ test("lists each release with its targets, cuts and the environments whose latest deploy names it", async () => {
44
+ await withTestDir(async (dir) => {
45
+ dirOf.current = dir;
46
+ initRepo(dir);
47
+ const r1 = release("1".repeat(40));
48
+ const r2 = release("2".repeat(40));
49
+ await persistRelease(r1, [], { cwd: dir });
50
+ await persistRelease(r2, [], { cwd: dir });
51
+ await appendCut({ releaseDigest: r1.releaseDigest, label: "v1", actor: "alice", timestamp: "2026-10-16T10:00:00.000Z", runOrigin: null }, { cwd: dir });
52
+ await appendCut({ releaseDigest: r2.releaseDigest, label: "v2", actor: "bob", timestamp: "2026-10-16T11:00:00.000Z", runOrigin: { forge: "github", repo: "o/r" } }, { cwd: dir });
53
+ await deploy("staging", "api", r2.releaseDigest);
54
+ await deploy("prod", "api", r1.releaseDigest);
55
+ await deploy("staging", "web");
56
+
57
+ const doc = await releasesDocument({ cwd: dir });
58
+ expectValid(doc);
59
+ if ("error" in doc) throw new Error(doc.error.message);
60
+ expect(doc).toMatchObject({ $schema: RELEASES_SCHEMA_ID, contract: 1, member: null, reason: null });
61
+ expect(doc.releases.map((r) => [r.label, r.environments])).toEqual([
62
+ ["v2", [{ env: "staging", components: ["api"] }]],
63
+ ["v1", [{ env: "prod", components: ["api"] }]],
64
+ ]);
65
+ expect(doc.releases[0]).toMatchObject({
66
+ releaseDigest: r2.releaseDigest,
67
+ commit: "2".repeat(40),
68
+ artifactDigests: r2.artifactDigests,
69
+ targets: r2.targets,
70
+ cuts: [{ label: "v2", actor: "bob", timestamp: "2026-10-16T11:00:00.000Z", runOrigin: { forge: "github", repo: "o/r" }, signature: null }],
71
+ });
72
+ });
73
+ });
74
+
75
+ test("a release file that is not a release and a cut line that is not a cut are left out under ledger-malformed", async () => {
76
+ await withTestDir(async (dir) => {
77
+ dirOf.current = dir;
78
+ initRepo(dir);
79
+ const r = release("3".repeat(40));
80
+ await persistRelease(r, [], { cwd: dir });
81
+ await writeLedgerFiles(
82
+ [
83
+ { path: `_releases/jcs1-sha256_${"9".repeat(64)}.json`, content: "{not json" },
84
+ { path: "_releases/cuts.jsonl", content: `{"version":1}\n` },
85
+ ],
86
+ "broken",
87
+ { cwd: dir },
88
+ );
89
+ const doc = await releasesDocument({ cwd: dir });
90
+ expectValid(doc);
91
+ if ("error" in doc) throw new Error(doc.error.message);
92
+ expect(doc.releases.map((x) => x.releaseDigest)).toEqual([r.releaseDigest]);
93
+ expect(doc.reason).toEqual({ code: "ledger-malformed", message: "1 release file(s) and 1 cut line(s) could not be read and are left out" });
94
+ });
95
+ });
96
+
97
+ test("an empty ledger lists no releases, and outside git the read fails with not-a-git-repository", async () => {
98
+ await withTestDir(async (dir) => {
99
+ const outside = await releasesDocument({ cwd: dir });
100
+ expectValid(outside);
101
+ expect(outside).toMatchObject({ error: { code: "not-a-git-repository" } });
102
+ initRepo(dir);
103
+ const empty = await releasesDocument({ cwd: dir });
104
+ expectValid(empty);
105
+ expect(empty).toMatchObject({ releases: [], reason: null });
106
+ });
107
+ });
108
+ });
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The `releases` read (#4026, ws-104): every release of one member with its
3
+ * targets, its cuts, and the environments that run it.
4
+ *
5
+ * `chant release list --json` prints this document. It reads the local
6
+ * `chant/lifecycle` branch and never fetches. An environment runs a release
7
+ * for the components whose latest deploy record there names the release in
8
+ * `releaseDigest`. A promote under a release writes that field; deploys made
9
+ * any other way do not name a release yet (#4043), so they list nowhere.
10
+ *
11
+ * Each release carries `revoked` (#4033, ws-105): the `ci/revoked/<sha>` tag
12
+ * on its commit, judged as `release cut` judges it, or null. It is read from
13
+ * local tags at read time; nothing records it. When the tags can't be judged
14
+ * (a `ci.green.signedSince` naming no commit here) every release reads null,
15
+ * and `chant workspace check` reports the declaration.
16
+ *
17
+ * Each cut carries `signatureCheck` (#4031): whether its signature verifies
18
+ * for its actor against the signers at base, and, when the declaration names
19
+ * a `release` gate with a class, whether the actor is in it.
20
+ *
21
+ * A release file that is not a release, or whose content does not hash to its
22
+ * digest, and a cut line that is not a cut, are left out and counted under
23
+ * `reason` with `ledger-malformed`; the rest are listed. A read that fails
24
+ * outright is the failure `ledger-unreadable`.
25
+ */
26
+
27
+ import { execFileSync } from "node:child_process";
28
+ import { latestPerComponent, listReleaseEnvironments, readReleaseLedger } from "../lifecycle/release-ledger";
29
+ import { readAllReleases, readCuts, type Release, type ReleaseCut } from "../lifecycle/release-store";
30
+ import { resolveMemberLedger } from "../lifecycle/member-ledger";
31
+ import { readerVersion } from "./declaration";
32
+ import { checkReleaseSignature, type GateAdmissionRule, type ReleaseSignatureCheck } from "./identity-gate-rule";
33
+ import { readRevokedCommits, revocationOf, type ReleaseRevocation } from "../lifecycle/release-revocation";
34
+
35
+ export const RELEASES_CONTRACT_VERSION = 1;
36
+ export const RELEASES_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/releases/v1/releases.schema.json";
37
+
38
+ /** Why the read stopped. Closed; each code is in `reason-codes.ts`. */
39
+ export const RELEASES_ERROR_CODES = ["not-a-git-repository", "ledger-unreadable"] as const;
40
+ /** Why some entries were left out. */
41
+ export const RELEASES_REASON_CODES = ["ledger-malformed"] as const;
42
+
43
+ /** One release as the document lists it. */
44
+ export interface ReleaseEntry extends Omit<Release, "version"> {
45
+ /** The label its cuts gave it, or null. */
46
+ label: string | null;
47
+ /** Its cuts, oldest first, each with whether its signature verifies against the signers at base (#4031). */
48
+ cuts: Array<Omit<ReleaseCut, "version" | "releaseDigest"> & { signatureCheck: ReleaseSignatureCheck }>;
49
+ /** The environments that run it, and in each the components whose latest deploy names it. Sorted by env. */
50
+ environments: Array<{ env: string; components: string[] }>;
51
+ /** The revoked tag on its commit (#4033), or null when the commit is not revoked. */
52
+ revoked: ReleaseRevocation | null;
53
+ }
54
+
55
+ export type ReleasesDocument =
56
+ | {
57
+ $schema: string;
58
+ contract: number;
59
+ chant: string;
60
+ /** The member read, its path of names joined with `/`, or null for a project outside a workspace. */
61
+ member: string | null;
62
+ /** Newest cut first; a release with no cut comes last. */
63
+ releases: ReleaseEntry[];
64
+ reason: { code: (typeof RELEASES_REASON_CODES)[number]; message: string } | null;
65
+ }
66
+ | { $schema: string; contract: number; chant: string; error: { code: (typeof RELEASES_ERROR_CODES)[number]; message: string } };
67
+
68
+ /**
69
+ * The rule a cut's signature is judged by (#4031): the declaration's
70
+ * `identity.gates.release` and the signers and role grants, all read at base.
71
+ */
72
+ export async function releaseRule(cwd: string): Promise<GateAdmissionRule> {
73
+ const { scopeSource } = await import("./write-scope");
74
+ const { releaseRuleFrom } = await import("./identity");
75
+ return releaseRuleFrom(scopeSource(cwd)).rule;
76
+ }
77
+
78
+ /** Read the releases of the member at `cwd`. */
79
+ export async function releasesDocument(req: { cwd: string }): Promise<ReleasesDocument> {
80
+ const head = { $schema: RELEASES_SCHEMA_ID, contract: RELEASES_CONTRACT_VERSION, chant: readerVersion() };
81
+ try {
82
+ execFileSync("git", ["rev-parse", "--git-dir"], { cwd: req.cwd, stdio: "ignore" });
83
+ } catch {
84
+ return { ...head, error: { code: "not-a-git-repository", message: "releases are read from the chant/lifecycle branch, and this directory is not in a git repository" } };
85
+ }
86
+ try {
87
+ const ledger = await resolveMemberLedger(req.cwd);
88
+ const loc = { cwd: req.cwd };
89
+ const { releases, malformed: badReleases } = await readAllReleases(loc);
90
+ const { cuts, malformed: badCuts } = await readCuts(loc);
91
+
92
+ const running = new Map<string, Map<string, string[]>>();
93
+ for (const env of await listReleaseEnvironments(loc)) {
94
+ const { records } = await readReleaseLedger(env, loc);
95
+ for (const record of latestPerComponent(records).values()) {
96
+ const digest = (record as { releaseDigest?: unknown }).releaseDigest;
97
+ if (typeof digest !== "string") continue;
98
+ const envs = running.get(digest) ?? new Map<string, string[]>();
99
+ envs.set(env, [...(envs.get(env) ?? []), record.component].sort());
100
+ running.set(digest, envs);
101
+ }
102
+ }
103
+
104
+ const rule = await releaseRule(req.cwd);
105
+ const revoked = await readRevokedCommits(req.cwd).catch(() => new Map<string, ReleaseRevocation>());
106
+ const entries: ReleaseEntry[] = releases.map(({ version: _v, ...release }) => {
107
+ const own = cuts.filter((c) => c.releaseDigest === release.releaseDigest);
108
+ return {
109
+ ...release,
110
+ label: own.find((c) => c.label !== null)?.label ?? null,
111
+ cuts: own.map(({ version: _cv, releaseDigest: _d, ...cut }) => ({ ...cut, signatureCheck: checkReleaseSignature(rule, { releaseDigest: release.releaseDigest, ...cut }) })),
112
+ environments: [...(running.get(release.releaseDigest) ?? new Map<string, string[]>())]
113
+ .map(([env, components]) => ({ env, components }))
114
+ .sort((a, b) => (a.env < b.env ? -1 : 1)),
115
+ revoked: revocationOf(release, revoked),
116
+ };
117
+ });
118
+ const latest = (e: ReleaseEntry): string => e.cuts.map((c) => c.timestamp).sort().at(-1) ?? "";
119
+ entries.sort((a, b) => (latest(b) !== latest(a) ? (latest(b) < latest(a) ? -1 : 1) : a.releaseDigest < b.releaseDigest ? -1 : 1));
120
+
121
+ const bad = badReleases + badCuts;
122
+ return {
123
+ ...head,
124
+ member: ledger.members.length > 0 ? ledger.members.join("/") : null,
125
+ releases: entries,
126
+ reason: bad > 0
127
+ ? { code: "ledger-malformed", message: `${badReleases} release file(s) and ${badCuts} cut line(s) could not be read and are left out` }
128
+ : null,
129
+ };
130
+ } catch (err) {
131
+ return { ...head, error: { code: "ledger-unreadable", message: `reading the releases failed: ${err instanceof Error ? err.message : String(err)}` } };
132
+ }
133
+ }
@@ -0,0 +1,295 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://intentius.io/chant/schemas/workspace/run-event/v1/run-event.schema.json",
4
+ "title": "chant run event",
5
+ "description": "One event of a run's stream (#4019, behold #678): one JSON object per line, written by writeRunEvent and createRunEventWriter in src/run-event.ts. It covers Op runs, component runs and pipeline runs alike. Every event names the run, what it ran, the commit it built from and the runtime it ran on, so a reader shows a run the same way wherever it happened. A run's stream opens with run-start and ends with run-settle, whose status is the run's verdict; seq counts the events from 0. Version 1 of the read contract. Readers ignore fields they do not know; a field is only ever added within a version. A status set gains a value only as an addition within a version, which the read contract lists with the chant that first writes it; a reader treats a status it does not know as a settle it cannot classify and reads on. Contract version 1 of this document is written by chant 0.124.28 and newer; the read contract as a whole by chant 0.81.0 and newer.",
6
+ "oneOf": [
7
+ { "$ref": "#/$defs/run-start" },
8
+ { "$ref": "#/$defs/run-settle" },
9
+ { "$ref": "#/$defs/wave-start" },
10
+ { "$ref": "#/$defs/wave-settle" },
11
+ { "$ref": "#/$defs/component-start" },
12
+ { "$ref": "#/$defs/component-settle" },
13
+ { "$ref": "#/$defs/phase-start" },
14
+ { "$ref": "#/$defs/phase-settle" },
15
+ { "$ref": "#/$defs/step-start" },
16
+ { "$ref": "#/$defs/step-settle" },
17
+ { "$ref": "#/$defs/gate-waiting" },
18
+ { "$ref": "#/$defs/gate-resolved" }
19
+ ],
20
+ "$defs": {
21
+ "envelope": {
22
+ "type": "object",
23
+ "description": "What every event carries.",
24
+ "required": ["$schema", "contract", "chant", "seq", "at", "type", "runId", "kind", "name", "gitSha", "runtime", "runOrigin"],
25
+ "properties": {
26
+ "$schema": { "const": "https://intentius.io/chant/schemas/workspace/run-event/v1/run-event.schema.json" },
27
+ "contract": { "const": 1 },
28
+ "chant": { "type": "string", "description": "The version of the chant that wrote the event." },
29
+ "seq": { "type": "integer", "minimum": 0, "description": "The event's place in its run's stream: 0 for run-start, one more for each event after it." },
30
+ "at": { "type": "string", "description": "When the event happened, ISO-8601." },
31
+ "type": { "type": "string" },
32
+ "runId": { "type": "string", "minLength": 1, "description": "The run's own id: an Op run's id, a component run's id (the id its release records carry, the CI run id in CI, else one minted locally), or the CI run id for a pipeline run. runOrigin says where it ran, which for an Op run in CI is the CI run, not the id space of runId." },
33
+ "kind": { "enum": ["op", "component", "pipeline"], "description": "What ran: an Op, a component selection (chant run --components), or a CI pipeline." },
34
+ "name": { "type": "string", "description": "The Op's name, the component selection as given, or the pipeline's name." },
35
+ "gitSha": {
36
+ "type": ["string", "null"],
37
+ "pattern": "^[0-9a-f]{40,64}$",
38
+ "description": "The commit the run built from, the same field a release record carries. Null when the run did not start in a git checkout."
39
+ },
40
+ "runtime": {
41
+ "enum": ["local", "steward", "ci"],
42
+ "description": "Where the run happened: in the process that started it, on a steward, or in a forge's CI."
43
+ },
44
+ "runOrigin": { "$ref": "#/$defs/runOrigin" }
45
+ }
46
+ },
47
+ "runOrigin": {
48
+ "type": "object",
49
+ "description": "Where the run ran and how to reach it: RunOrigin from lifecycle/release-ledger.ts, the shape a release record's runOrigin has. For an Op run in CI it locates the CI run, as the Op's run record does (#4020), while runId stays the Op run's own id.",
50
+ "required": ["forge"],
51
+ "properties": {
52
+ "forge": {
53
+ "enum": ["github", "gitlab", "op", "local"],
54
+ "description": "github: a GitHub Actions run, also the spelling Forgejo Actions uses. gitlab: a CI pipeline. op: a steward's run, named by its Op run id. local: a run on the machine that started it, outside CI."
55
+ },
56
+ "repo": { "type": "string", "description": "The repository or project slug on the forge, when the environment named one." },
57
+ "url": { "type": "string", "description": "A link to the run, when the environment gave enough to build one." }
58
+ }
59
+ },
60
+ "settleStatus": {
61
+ "enum": ["ok", "failed", "gated", "waiting"],
62
+ "description": "How a run, wave, component or phase settled. gated: it stopped at a gate nobody has approved. waiting: it stopped at a decision point nobody has answered. Neither is a failure."
63
+ },
64
+ "stepIndex": { "type": "integer", "minimum": 0, "description": "The step's place in its phase as authored, counting from 0, so two steps of one phase that share a name stay apart. Optional, added within contract 1 by chant 0.124.28 (#4041). A writer leaves it out for a step it has no authored place for, and a reader without it pairs a step-start with its step-settle by component, phase and step in stream order." },
65
+ "component": {
66
+ "type": ["string", "null"],
67
+ "description": "The component whose composition the phase or step belongs to in a component run, and null in an Op or pipeline run."
68
+ },
69
+ "run-start": {
70
+ "allOf": [
71
+ { "$ref": "#/$defs/envelope" },
72
+ {
73
+ "type": "object",
74
+ "description": "The run is about to start.",
75
+ "required": ["type", "waves"],
76
+ "properties": {
77
+ "type": { "const": "run-start" },
78
+ "waves": {
79
+ "type": ["array", "null"],
80
+ "items": { "type": "array", "items": { "type": "string" } },
81
+ "description": "In a component run, the waves of components the run will attempt, in order; wave numbers in later events count from 1 into this list. Null in an Op or pipeline run."
82
+ }
83
+ }
84
+ }
85
+ ]
86
+ },
87
+ "run-settle": {
88
+ "allOf": [
89
+ { "$ref": "#/$defs/envelope" },
90
+ {
91
+ "type": "object",
92
+ "description": "The run settled. The last event of the stream, and its verdict.",
93
+ "required": ["type", "status", "durationMs"],
94
+ "properties": {
95
+ "type": { "const": "run-settle" },
96
+ "status": { "$ref": "#/$defs/settleStatus" },
97
+ "durationMs": { "type": ["integer", "null"], "minimum": 0, "description": "From run-start to now, or null when the writer did not time it." },
98
+ "error": { "type": "string", "description": "With status failed, what failed, when the writer knows." }
99
+ }
100
+ }
101
+ ]
102
+ },
103
+ "wave-start": {
104
+ "allOf": [
105
+ { "$ref": "#/$defs/envelope" },
106
+ {
107
+ "type": "object",
108
+ "description": "A wave of a component run is about to start. Its components may run at the same time.",
109
+ "required": ["type", "wave", "components"],
110
+ "properties": {
111
+ "type": { "const": "wave-start" },
112
+ "wave": { "type": "integer", "minimum": 1 },
113
+ "components": { "type": "array", "items": { "type": "string" } }
114
+ }
115
+ }
116
+ ]
117
+ },
118
+ "wave-settle": {
119
+ "allOf": [
120
+ { "$ref": "#/$defs/envelope" },
121
+ {
122
+ "type": "object",
123
+ "description": "A wave settled. failed if any component in it failed, which stops the run before the next wave.",
124
+ "required": ["type", "wave", "status"],
125
+ "properties": {
126
+ "type": { "const": "wave-settle" },
127
+ "wave": { "type": "integer", "minimum": 1 },
128
+ "status": { "$ref": "#/$defs/settleStatus" }
129
+ }
130
+ }
131
+ ]
132
+ },
133
+ "component-start": {
134
+ "allOf": [
135
+ { "$ref": "#/$defs/envelope" },
136
+ {
137
+ "type": "object",
138
+ "description": "A component in a wave is about to start its deploy composition.",
139
+ "required": ["type", "wave", "component"],
140
+ "properties": {
141
+ "type": { "const": "component-start" },
142
+ "wave": { "type": "integer", "minimum": 1 },
143
+ "component": { "type": "string" }
144
+ }
145
+ }
146
+ ]
147
+ },
148
+ "component-settle": {
149
+ "allOf": [
150
+ { "$ref": "#/$defs/envelope" },
151
+ {
152
+ "type": "object",
153
+ "description": "A component's composition settled, after any rollback the run did for it. A component the run did not start settles too, with no component-start before it: blocked when a dependency failed or stopped at a gate, already-applied when an earlier attempt the run resumed applied it.",
154
+ "required": ["type", "wave", "component", "status"],
155
+ "properties": {
156
+ "type": { "const": "component-settle" },
157
+ "wave": { "type": "integer", "minimum": 1 },
158
+ "component": { "type": "string" },
159
+ "status": {
160
+ "enum": ["ok", "failed", "gated", "waiting", "blocked", "already-applied"],
161
+ "description": "The settle statuses of a run, wave or phase, and two more for a component the run did not start, added within contract version 1 by #4007 and written by chant 0.124.29 and newer. blocked: a dependency failed or stopped at a gate, so it never ran; blockedBy names the dependency. already-applied: an earlier attempt the run resumed applied it, so it did not run again; its wave is the wave it had in that attempt's derivation. A reader that knows only the four settle statuses can read either as a component that did not run in this run."
162
+ },
163
+ "blockedBy": {
164
+ "type": "string",
165
+ "description": "With blocked: the failed or gated component the run reached this one from. Added within contract version 1 by #4007, written by chant 0.124.29 and newer."
166
+ }
167
+ }
168
+ }
169
+ ]
170
+ },
171
+ "phase-start": {
172
+ "allOf": [
173
+ { "$ref": "#/$defs/envelope" },
174
+ {
175
+ "type": "object",
176
+ "description": "A phase is about to run its steps: an Op's phase, a component composition's phase, or a pipeline's stage.",
177
+ "required": ["type", "component", "phase"],
178
+ "properties": {
179
+ "type": { "const": "phase-start" },
180
+ "component": { "$ref": "#/$defs/component" },
181
+ "phase": { "type": "string" }
182
+ }
183
+ }
184
+ ]
185
+ },
186
+ "phase-settle": {
187
+ "allOf": [
188
+ { "$ref": "#/$defs/envelope" },
189
+ {
190
+ "type": "object",
191
+ "description": "A phase settled: ok when every step in it settled ok, skipped or cached.",
192
+ "required": ["type", "component", "phase", "status"],
193
+ "properties": {
194
+ "type": { "const": "phase-settle" },
195
+ "component": { "$ref": "#/$defs/component" },
196
+ "phase": { "type": "string" },
197
+ "status": { "$ref": "#/$defs/settleStatus" }
198
+ }
199
+ }
200
+ ]
201
+ },
202
+ "step-start": {
203
+ "allOf": [
204
+ { "$ref": "#/$defs/envelope" },
205
+ {
206
+ "type": "object",
207
+ "description": "A step is about to run: an Op or composition step, or a pipeline's job. A step that never started, such as one an earlier failure stopped, has a step-settle and no step-start.",
208
+ "required": ["type", "component", "phase", "step"],
209
+ "properties": {
210
+ "type": { "const": "step-start" },
211
+ "component": { "$ref": "#/$defs/component" },
212
+ "phase": { "type": "string" },
213
+ "step": { "type": "string", "description": "The step's activity, gate:<name> for a gate, effect:<name> for an effect, or the job's name." },
214
+ "index": { "$ref": "#/$defs/stepIndex" }
215
+ }
216
+ }
217
+ ]
218
+ },
219
+ "step-settle": {
220
+ "allOf": [
221
+ { "$ref": "#/$defs/envelope" },
222
+ {
223
+ "type": "object",
224
+ "description": "A step settled.",
225
+ "required": ["type", "component", "phase", "step", "status", "durationMs"],
226
+ "properties": {
227
+ "type": { "const": "step-settle" },
228
+ "component": { "$ref": "#/$defs/component" },
229
+ "phase": { "type": "string" },
230
+ "step": { "type": "string" },
231
+ "index": { "$ref": "#/$defs/stepIndex" },
232
+ "status": {
233
+ "enum": ["ok", "failed", "skipped", "cached", "gated", "waiting"],
234
+ "description": "ok: it ran and succeeded. failed: it ran and failed. skipped: it did not run, and skip says why. cached: a cache hit stood in for running it (#4015). gated: it stopped the run at a gate nobody has approved. waiting: it stopped the run at a decision point nobody has answered."
235
+ },
236
+ "skip": {
237
+ "enum": ["receipt", "resumed", "refused", "not-run", null],
238
+ "description": "With status skipped. receipt: the effect's receipt matched, so it had already applied. resumed: the run resumed at a later gate and carried this step's output from an earlier run. refused: it declined to go on, and message says why. not-run: an earlier step failed or stopped the run. Null when the writer does not know."
239
+ },
240
+ "durationMs": { "type": ["integer", "null"], "minimum": 0, "description": "How long it ran, or null when the writer did not time it." },
241
+ "error": { "type": "string", "description": "With status failed, what failed." },
242
+ "message": { "type": "string", "description": "Why a step skipped, gated or waited, for a person." }
243
+ }
244
+ }
245
+ ]
246
+ },
247
+ "gate-waiting": {
248
+ "allOf": [
249
+ { "$ref": "#/$defs/envelope" },
250
+ {
251
+ "type": "object",
252
+ "description": "The run stopped and waits on a person: a gate nobody has approved, or a decision point nobody has answered. The step settles gated or waiting next.",
253
+ "required": ["type", "component", "phase", "step", "op", "gate", "point"],
254
+ "properties": {
255
+ "type": { "const": "gate-waiting" },
256
+ "component": { "$ref": "#/$defs/component" },
257
+ "phase": { "type": "string" },
258
+ "step": { "type": "string" },
259
+ "index": { "$ref": "#/$defs/stepIndex" },
260
+ "op": { "type": "string", "description": "The op the gate is recorded under, which chant approve takes. It differs from name when a step's command stopped at a gate of its own." },
261
+ "gate": { "type": ["string", "null"], "description": "The gate's name, or null when the step waits on a decision point alone." },
262
+ "point": { "type": ["string", "null"], "description": "The answer record id of the open question, which points answer takes, or null." },
263
+ "message": { "type": "string", "description": "Why the gate did not pass, such as an approval for a different plan." }
264
+ }
265
+ }
266
+ ]
267
+ },
268
+ "gate-resolved": {
269
+ "allOf": [
270
+ { "$ref": "#/$defs/envelope" },
271
+ {
272
+ "type": "object",
273
+ "description": "A gate the run reached was approved, so its step goes on.",
274
+ "required": ["type", "component", "phase", "step", "op", "gate", "resolvedBy", "resolvedAt", "via"],
275
+ "properties": {
276
+ "type": { "const": "gate-resolved" },
277
+ "component": { "$ref": "#/$defs/component" },
278
+ "phase": { "type": "string" },
279
+ "step": { "type": "string" },
280
+ "index": { "$ref": "#/$defs/stepIndex" },
281
+ "op": { "type": "string" },
282
+ "gate": { "type": "string" },
283
+ "resolvedBy": { "type": "string", "description": "Who approved it." },
284
+ "resolvedAt": { "type": "string", "description": "When the approval was recorded, ISO-8601." },
285
+ "via": {
286
+ "enum": ["quorum", "policy", "point", "alias", "empty", null],
287
+ "description": "How the approval passed when it was more than one person's approval of this gate: a quorum, a policy permit, a decision point's answer, or an approval recorded under one of the gate's aliases. empty (added within contract 1 by #4045, chant 0.124.28 and newer): a release gate passed over an empty change set with no approval, on the resolution the promote wrote, whose resolvedBy is the actor running it."
288
+ },
289
+ "url": { "type": "string", "description": "Where the approval was made, when known." }
290
+ }
291
+ }
292
+ ]
293
+ }
294
+ }
295
+ }
@@ -113,7 +113,7 @@ function fixture(build: () => string): () => string {
113
113
 
114
114
  let n = 0;
115
115
  /** One ledger line. */
116
- function release(component: string, env: string, digest: string, gitSha: string, extra: Record<string, string> = {}): string {
116
+ function release(component: string, env: string, digest: string, gitSha: string, extra: Record<string, unknown> = {}): string {
117
117
  n++;
118
118
  return JSON.stringify({
119
119
  version: 1,
@@ -452,6 +452,61 @@ const gated = fixture(() => {
452
452
  return root;
453
453
  });
454
454
 
455
+ describe("commit, run and gate on the records status --json lists (#4020)", () => {
456
+ test("a release names the gate that admitted it, a gate its commit and run, and an older record gives null", async () => {
457
+ const root = twoLayouts();
458
+ const sha = "a".repeat(40);
459
+ const admission = { op: "web", gate: "deploy", resolvedAt: "2026-09-20T01:00:00.000Z" };
460
+ lifecycle(root, {
461
+ "_members/web/staging/releases.jsonl": jsonl(release("web", "staging", D("a"), sha, { approver: "alice", admission })),
462
+ "_members/web/_gates/web.jsonl": jsonl(
463
+ pending("web", "deploy", "2026-09-20T00:00:00.000Z", { environment: "staging", planDigest: D("1"), gitSha: sha, runId: "9001" }),
464
+ approval("web", "deploy", "alice", "2026-09-20T01:00:00.000Z", { environment: "staging", planDigest: D("1"), gitSha: sha, runId: "9001" }),
465
+ pending("web", "old", "2026-09-20T00:00:00.000Z", { environment: "staging", planDigest: D("2") }),
466
+ ),
467
+ "staging/releases.jsonl": jsonl(release("api", "staging", D("b"), "b".repeat(40))),
468
+ });
469
+ const doc = result(await workspaceStatus({ cwd: root, env: "staging", now: NOW }));
470
+ expectValid(doc);
471
+ const [, web, api] = doc.members;
472
+ expect(web.environments[0].releases[0]).toMatchObject({ gitSha: sha, approver: "alice", admission });
473
+ expect(api.environments[0].releases[0]).toMatchObject({ approver: null, admission: null });
474
+ const [deploy, old] = web.gates;
475
+ expect(deploy).toMatchObject({ name: "deploy", gitSha: sha, runId: "9001" });
476
+ expect(old).not.toHaveProperty("gitSha");
477
+ expect(old).not.toHaveProperty("runId");
478
+ });
479
+ });
480
+
481
+ describe("the release a deploy was made from, on the records status --json lists (#4043)", () => {
482
+ test("a release item carries the releaseDigest and the label it was cut with, and null when there is none", async () => {
483
+ const root = twoLayouts();
484
+ const rd = `jcs1-sha256:${"e".repeat(64)}`;
485
+ const cut = JSON.stringify({ version: 1, releaseDigest: rd, label: "v1.2.0", actor: "alice", timestamp: "2026-09-19T00:00:00.000Z", runOrigin: null, signature: null });
486
+ lifecycle(root, {
487
+ "_members/web/staging/releases.jsonl": jsonl(release("web", "staging", D("a"), "a".repeat(40), { releaseDigest: rd })),
488
+ "_members/web/_releases/cuts.jsonl": `${cut}\n`,
489
+ "staging/releases.jsonl": jsonl(release("api", "staging", D("b"), "b".repeat(40))),
490
+ });
491
+ const doc = result(await workspaceStatus({ cwd: root, env: "staging", now: NOW }));
492
+ expectValid(doc);
493
+ const [, web, api] = doc.members;
494
+ expect(web.environments[0].releases[0]).toMatchObject({ releaseDigest: rd, releaseLabel: "v1.2.0" });
495
+ expect(api.environments[0].releases[0]).toMatchObject({ releaseDigest: null, releaseLabel: null });
496
+ });
497
+
498
+ test("a release with no cut line keeps its digest and has no label", async () => {
499
+ const root = twoLayouts();
500
+ const rd = `jcs1-sha256:${"e".repeat(64)}`;
501
+ lifecycle(root, {
502
+ "_members/web/staging/releases.jsonl": jsonl(release("web", "staging", D("a"), "a".repeat(40), { releaseDigest: rd })),
503
+ });
504
+ const doc = result(await workspaceStatus({ cwd: root, env: "staging", now: NOW }));
505
+ expectValid(doc);
506
+ expect(doc.members[1].environments[0].releases[0]).toMatchObject({ releaseDigest: rd, releaseLabel: null });
507
+ });
508
+ });
509
+
455
510
  describe("gate state in chant workspace status --json (#2674)", () => {
456
511
  test("each member lists its gates with state, approvals, quorum and the approve line", async () => {
457
512
  const doc = result(await workspaceStatus({ cwd: gated(), env: "staging", compareTo: "prod", now: NOW }));
@@ -766,4 +821,29 @@ describe("stewards in chant workspace status --json (#2731)", () => {
766
821
  expect(doc.members[0].stewards.map((s) => s.name)).toEqual(["box-steward"]);
767
822
  expect(doc.members[0].stewardReasons.map((r) => r.code)).toEqual(["stewards-unreadable"]);
768
823
  });
824
+
825
+ test("a member lists its promotion order from the environments array (#4030)", async () => {
826
+ const root = stewarded();
827
+ writeFileSync(
828
+ join(root, "box", "chant.config.ts"),
829
+ 'export default { environments: ["dev", { name: "staging", canary: true }, "prod", "pr-*"] };\n',
830
+ );
831
+ const doc = result(await workspaceStatus({ cwd: root, env: "minimal" }));
832
+ expectValid(doc);
833
+ expect(doc.members[0].promotionOrder).toEqual([
834
+ { name: "dev", canary: false },
835
+ { name: "staging", canary: true },
836
+ { name: "prod", canary: false },
837
+ ]);
838
+ // A member with no chant.config.ts declares none.
839
+ expect(doc.members[1].promotionOrder).toEqual([]);
840
+ });
841
+
842
+ test("a promotion order the config can't give is null (#4030)", async () => {
843
+ const root = stewarded();
844
+ writeFileSync(join(root, "box", "chant.config.ts"), "throw new Error(\"broken\");\n");
845
+ const doc = result(await workspaceStatus({ cwd: root, env: "minimal" }));
846
+ expectValid(doc);
847
+ expect(doc.members[0].promotionOrder).toBeNull();
848
+ });
769
849
  });