@intentius/chant 0.124.27 → 0.124.28

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 (450) 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/change-set-graph.d.ts +56 -0
  20. package/dist/change-set-graph.d.ts.map +1 -0
  21. package/dist/change-set-graph.js +128 -0
  22. package/dist/change-set.d.ts +52 -3
  23. package/dist/change-set.d.ts.map +1 -1
  24. package/dist/change-set.js +35 -4
  25. package/dist/chant-version.d.ts +9 -0
  26. package/dist/chant-version.d.ts.map +1 -0
  27. package/dist/chant-version.js +16 -0
  28. package/dist/cli/commands/build.d.ts +8 -0
  29. package/dist/cli/commands/build.d.ts.map +1 -1
  30. package/dist/cli/commands/build.js +9 -8
  31. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  32. package/dist/cli/commands/check-lexicon-docs.js +30 -2
  33. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  34. package/dist/cli/commands/check-lexicon.js +13 -2
  35. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
  36. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  37. package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
  38. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  39. package/dist/cli/commands/init-lexicon.js +17 -8
  40. package/dist/cli/handlers/approve.d.ts.map +1 -1
  41. package/dist/cli/handlers/approve.js +19 -4
  42. package/dist/cli/handlers/build.d.ts.map +1 -1
  43. package/dist/cli/handlers/build.js +28 -0
  44. package/dist/cli/handlers/change-set.d.ts +10 -0
  45. package/dist/cli/handlers/change-set.d.ts.map +1 -1
  46. package/dist/cli/handlers/change-set.js +51 -1
  47. package/dist/cli/handlers/fan-out.d.ts +1 -1
  48. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  49. package/dist/cli/handlers/fan-out.js +14 -3
  50. package/dist/cli/handlers/pr.d.ts +8 -2
  51. package/dist/cli/handlers/pr.d.ts.map +1 -1
  52. package/dist/cli/handlers/pr.js +148 -24
  53. package/dist/cli/handlers/promote.d.ts.map +1 -1
  54. package/dist/cli/handlers/promote.js +256 -21
  55. package/dist/cli/handlers/release.d.ts +34 -0
  56. package/dist/cli/handlers/release.d.ts.map +1 -0
  57. package/dist/cli/handlers/release.js +203 -0
  58. package/dist/cli/handlers/run.d.ts +28 -2
  59. package/dist/cli/handlers/run.d.ts.map +1 -1
  60. package/dist/cli/handlers/run.js +170 -13
  61. package/dist/cli/main.d.ts.map +1 -1
  62. package/dist/cli/main.js +88 -13
  63. package/dist/cli/mcp/workspace-tools.js +1 -1
  64. package/dist/cli/parse-args.d.ts.map +1 -1
  65. package/dist/cli/parse-args.js +49 -3
  66. package/dist/cli/registry.d.ts +29 -3
  67. package/dist/cli/registry.d.ts.map +1 -1
  68. package/dist/cli/run-events.d.ts +29 -0
  69. package/dist/cli/run-events.d.ts.map +1 -0
  70. package/dist/cli/run-events.js +47 -0
  71. package/dist/cli/version.d.ts +3 -5
  72. package/dist/cli/version.d.ts.map +1 -1
  73. package/dist/cli/version.js +3 -13
  74. package/dist/codegen/docs-in-site.d.ts +71 -0
  75. package/dist/codegen/docs-in-site.d.ts.map +1 -0
  76. package/dist/codegen/docs-in-site.js +177 -0
  77. package/dist/codegen/docs-types.d.ts +14 -1
  78. package/dist/codegen/docs-types.d.ts.map +1 -1
  79. package/dist/codegen/docs.d.ts +18 -1
  80. package/dist/codegen/docs.d.ts.map +1 -1
  81. package/dist/codegen/docs.js +40 -7
  82. package/dist/codegen/rehype-base-url.mjs +15 -9
  83. package/dist/components/auto-release.d.ts +4 -1
  84. package/dist/components/auto-release.d.ts.map +1 -1
  85. package/dist/components/auto-release.js +2 -0
  86. package/dist/components/cli-support.d.ts +6 -0
  87. package/dist/components/cli-support.d.ts.map +1 -1
  88. package/dist/components/cli-support.js +10 -9
  89. package/dist/components/driver.d.ts +42 -9
  90. package/dist/components/driver.d.ts.map +1 -1
  91. package/dist/components/driver.js +81 -54
  92. package/dist/components/fan-out-run.d.ts +7 -0
  93. package/dist/components/fan-out-run.d.ts.map +1 -1
  94. package/dist/components/fan-out-run.js +35 -26
  95. package/dist/components/pr-run.d.ts +11 -0
  96. package/dist/components/pr-run.d.ts.map +1 -1
  97. package/dist/components/pr-run.js +20 -1
  98. package/dist/components/promote-order.d.ts +38 -0
  99. package/dist/components/promote-order.d.ts.map +1 -0
  100. package/dist/components/promote-order.js +62 -0
  101. package/dist/components/promote.d.ts +15 -2
  102. package/dist/components/promote.d.ts.map +1 -1
  103. package/dist/components/promote.js +17 -0
  104. package/dist/components/release-compile.d.ts +133 -0
  105. package/dist/components/release-compile.d.ts.map +1 -0
  106. package/dist/components/release-compile.js +376 -0
  107. package/dist/components/release-gate.d.ts +125 -0
  108. package/dist/components/release-gate.d.ts.map +1 -0
  109. package/dist/components/release-gate.js +282 -0
  110. package/dist/components/release-promote.d.ts +96 -0
  111. package/dist/components/release-promote.d.ts.map +1 -0
  112. package/dist/components/release-promote.js +204 -0
  113. package/dist/components/run-progress.d.ts +7 -0
  114. package/dist/components/run-progress.d.ts.map +1 -1
  115. package/dist/components/wave-plan.d.ts +3 -1
  116. package/dist/components/wave-plan.d.ts.map +1 -1
  117. package/dist/components/wave-plan.js +1 -1
  118. package/dist/config.d.ts +7 -0
  119. package/dist/config.d.ts.map +1 -1
  120. package/dist/config.js +1 -0
  121. package/dist/index.d.ts +1 -0
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +2 -0
  124. package/dist/lifecycle/change-set-store.d.ts +67 -0
  125. package/dist/lifecycle/change-set-store.d.ts.map +1 -0
  126. package/dist/lifecycle/change-set-store.js +96 -0
  127. package/dist/lifecycle/commit-sha.d.ts +13 -0
  128. package/dist/lifecycle/commit-sha.d.ts.map +1 -0
  129. package/dist/lifecycle/commit-sha.js +21 -0
  130. package/dist/lifecycle/gate-change-set.d.ts +69 -0
  131. package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
  132. package/dist/lifecycle/gate-change-set.js +106 -0
  133. package/dist/lifecycle/gate-ledger.d.ts +49 -1
  134. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  135. package/dist/lifecycle/gate-ledger.js +18 -0
  136. package/dist/lifecycle/release-cut.d.ts +107 -0
  137. package/dist/lifecycle/release-cut.d.ts.map +1 -0
  138. package/dist/lifecycle/release-cut.js +273 -0
  139. package/dist/lifecycle/release-ledger.d.ts +54 -0
  140. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  141. package/dist/lifecycle/release-ledger.js +53 -20
  142. package/dist/lifecycle/release-store.d.ts +190 -0
  143. package/dist/lifecycle/release-store.d.ts.map +1 -0
  144. package/dist/lifecycle/release-store.js +318 -0
  145. package/dist/lifecycle/run-ledger.d.ts +4 -0
  146. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  147. package/dist/lifecycle/run-ledger.js +2 -0
  148. package/dist/op/activities/change-coverage.d.ts.map +1 -1
  149. package/dist/op/activities/change-coverage.js +4 -2
  150. package/dist/op/activities/source-release.d.ts.map +1 -1
  151. package/dist/op/activities/source-release.js +7 -3
  152. package/dist/op/activities/source-rollback.d.ts.map +1 -1
  153. package/dist/op/activities/source-rollback.js +7 -3
  154. package/dist/op/gate-name.d.ts +11 -2
  155. package/dist/op/gate-name.d.ts.map +1 -1
  156. package/dist/op/gate-name.js +12 -3
  157. package/dist/op/gate-run-locator.d.ts +22 -0
  158. package/dist/op/gate-run-locator.d.ts.map +1 -1
  159. package/dist/op/gate-run-locator.js +31 -0
  160. package/dist/op/gate.d.ts +27 -3
  161. package/dist/op/gate.d.ts.map +1 -1
  162. package/dist/op/gate.js +57 -3
  163. package/dist/op/local-executor.d.ts +36 -1
  164. package/dist/op/local-executor.d.ts.map +1 -1
  165. package/dist/op/local-executor.js +158 -52
  166. package/dist/op/op-waves-run.d.ts.map +1 -1
  167. package/dist/op/op-waves-run.js +4 -0
  168. package/dist/op/run-live.d.ts +2 -0
  169. package/dist/op/run-live.d.ts.map +1 -1
  170. package/dist/op/run-live.js +1 -1
  171. package/dist/op/runtime.d.ts +46 -0
  172. package/dist/op/runtime.d.ts.map +1 -1
  173. package/dist/op/runtimes/local.d.ts.map +1 -1
  174. package/dist/op/runtimes/local.js +8 -0
  175. package/dist/point-decision.d.ts +2 -0
  176. package/dist/point-decision.d.ts.map +1 -1
  177. package/dist/point-decision.js +9 -2
  178. package/dist/run-event-store.d.ts +77 -0
  179. package/dist/run-event-store.d.ts.map +1 -0
  180. package/dist/run-event-store.js +204 -0
  181. package/dist/run-event-stream.d.ts +132 -0
  182. package/dist/run-event-stream.d.ts.map +1 -0
  183. package/dist/run-event-stream.js +238 -0
  184. package/dist/run-event.d.ts +267 -0
  185. package/dist/run-event.d.ts.map +1 -0
  186. package/dist/run-event.js +207 -0
  187. package/dist/wave-runtime.mjs +204 -199
  188. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  189. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  190. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  191. package/dist/workspace/change-set-show.d.ts +56 -0
  192. package/dist/workspace/change-set-show.d.ts.map +1 -0
  193. package/dist/workspace/change-set-show.js +54 -0
  194. package/dist/workspace/change-set-show.schema.json +102 -0
  195. package/dist/workspace/change-set.schema.json +28 -1
  196. package/dist/workspace/changes-cli.d.ts.map +1 -1
  197. package/dist/workspace/changes-cli.js +10 -1
  198. package/dist/workspace/changes.d.ts +37 -0
  199. package/dist/workspace/changes.d.ts.map +1 -1
  200. package/dist/workspace/changes.js +49 -2
  201. package/dist/workspace/changes.schema.json +135 -0
  202. package/dist/workspace/ci-green-cli.d.ts +19 -4
  203. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  204. package/dist/workspace/ci-green-cli.js +75 -12
  205. package/dist/workspace/ci-green-forge.d.ts +20 -3
  206. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  207. package/dist/workspace/ci-green-forge.js +6 -4
  208. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  209. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  210. package/dist/workspace/ci-green-workflow.js +6 -0
  211. package/dist/workspace/ci-green.d.ts +273 -13
  212. package/dist/workspace/ci-green.d.ts.map +1 -1
  213. package/dist/workspace/ci-green.js +449 -38
  214. package/dist/workspace/ci-last-green.schema.json +32 -2
  215. package/dist/workspace/ci-list.schema.json +525 -0
  216. package/dist/workspace/commit-links.d.ts +85 -0
  217. package/dist/workspace/commit-links.d.ts.map +1 -0
  218. package/dist/workspace/commit-links.js +163 -0
  219. package/dist/workspace/declaration.d.ts +19 -0
  220. package/dist/workspace/declaration.d.ts.map +1 -1
  221. package/dist/workspace/declaration.js +1 -1
  222. package/dist/workspace/declaration.schema.json +12 -0
  223. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  224. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  225. package/dist/workspace/identity-gate-rule.js +72 -7
  226. package/dist/workspace/identity.d.ts +12 -0
  227. package/dist/workspace/identity.d.ts.map +1 -1
  228. package/dist/workspace/identity.js +13 -1
  229. package/dist/workspace/intent.d.ts +6 -1
  230. package/dist/workspace/intent.d.ts.map +1 -1
  231. package/dist/workspace/intent.js +51 -1
  232. package/dist/workspace/ls.d.ts +4 -0
  233. package/dist/workspace/ls.d.ts.map +1 -1
  234. package/dist/workspace/ls.js +2 -0
  235. package/dist/workspace/ls.schema.json +3 -1
  236. package/dist/workspace/member-pipeline.d.ts +6 -0
  237. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  238. package/dist/workspace/member-pipeline.js +6 -0
  239. package/dist/workspace/promotion-order.d.ts +41 -0
  240. package/dist/workspace/promotion-order.d.ts.map +1 -0
  241. package/dist/workspace/promotion-order.js +95 -0
  242. package/dist/workspace/reason-codes.d.ts +2 -0
  243. package/dist/workspace/reason-codes.d.ts.map +1 -1
  244. package/dist/workspace/reason-codes.js +4 -1
  245. package/dist/workspace/releases.d.ts +72 -0
  246. package/dist/workspace/releases.d.ts.map +1 -0
  247. package/dist/workspace/releases.js +94 -0
  248. package/dist/workspace/releases.schema.json +391 -0
  249. package/dist/workspace/run-event.schema.json +288 -0
  250. package/dist/workspace/status-gates.d.ts +26 -1
  251. package/dist/workspace/status-gates.d.ts.map +1 -1
  252. package/dist/workspace/status-gates.js +31 -2
  253. package/dist/workspace/status-stewards.d.ts +4 -0
  254. package/dist/workspace/status-stewards.d.ts.map +1 -1
  255. package/dist/workspace/status-stewards.js +2 -0
  256. package/dist/workspace/status.d.ts +39 -0
  257. package/dist/workspace/status.d.ts.map +1 -1
  258. package/dist/workspace/status.js +48 -1
  259. package/dist/workspace/status.schema.json +70 -1
  260. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  261. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  262. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  263. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  264. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  265. package/dist/workspace/trust/op-run-statement.js +143 -0
  266. package/dist/workspace/trust/rotation.d.ts +18 -2
  267. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  268. package/dist/workspace/trust/rotation.js +65 -16
  269. package/dist/workspace/trust/seal.d.ts +11 -0
  270. package/dist/workspace/trust/seal.d.ts.map +1 -1
  271. package/dist/workspace/trust/seal.js +9 -0
  272. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  273. package/dist/workspace/trust/ssh-commit.js +10 -10
  274. package/package.json +1 -1
  275. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  276. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  277. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  278. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  279. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  280. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  281. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  282. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  283. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  284. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  285. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  286. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  287. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  288. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  289. package/src/__golden__/wave-loop/op-spend.json +3 -0
  290. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  291. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  292. package/src/change-set-graph.test.ts +72 -0
  293. package/src/change-set-graph.ts +133 -0
  294. package/src/change-set.ts +76 -6
  295. package/src/chant-version.ts +16 -0
  296. package/src/cli/commands/build.ts +19 -8
  297. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  298. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  299. package/src/cli/commands/check-lexicon.ts +14 -2
  300. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  301. package/src/cli/commands/init-lexicon.test.ts +25 -0
  302. package/src/cli/commands/init-lexicon.ts +18 -8
  303. package/src/cli/handlers/approve-release.test.ts +80 -0
  304. package/src/cli/handlers/approve.ts +18 -2
  305. package/src/cli/handlers/build.test.ts +52 -0
  306. package/src/cli/handlers/build.ts +29 -0
  307. package/src/cli/handlers/change-set.ts +49 -1
  308. package/src/cli/handlers/fan-out.test.ts +48 -0
  309. package/src/cli/handlers/fan-out.ts +15 -3
  310. package/src/cli/handlers/operator.test.ts +37 -0
  311. package/src/cli/handlers/pr.test.ts +194 -0
  312. package/src/cli/handlers/pr.ts +168 -34
  313. package/src/cli/handlers/promote.test.ts +389 -5
  314. package/src/cli/handlers/promote.ts +279 -20
  315. package/src/cli/handlers/release-implicit.e2e.test.ts +153 -0
  316. package/src/cli/handlers/release.e2e.test.ts +85 -0
  317. package/src/cli/handlers/release.ts +206 -0
  318. package/src/cli/handlers/run-auto-release.test.ts +71 -0
  319. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  320. package/src/cli/handlers/run.test.ts +30 -0
  321. package/src/cli/handlers/run.ts +176 -15
  322. package/src/cli/main.test.ts +7 -0
  323. package/src/cli/main.ts +88 -13
  324. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  325. package/src/cli/mcp/workspace-tools.ts +1 -1
  326. package/src/cli/parse-args.ts +37 -3
  327. package/src/cli/registry.ts +29 -3
  328. package/src/cli/run-events.ts +54 -0
  329. package/src/cli/version.ts +3 -13
  330. package/src/codegen/docs-in-site.test.ts +166 -0
  331. package/src/codegen/docs-in-site.ts +182 -0
  332. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  333. package/src/codegen/docs-types.ts +14 -1
  334. package/src/codegen/docs.ts +60 -7
  335. package/src/codegen/rehype-base-url.d.mts +3 -1
  336. package/src/codegen/rehype-base-url.mjs +15 -9
  337. package/src/codegen/rehype-base-url.test.ts +34 -0
  338. package/src/components/auto-release.test.ts +21 -0
  339. package/src/components/auto-release.ts +6 -1
  340. package/src/components/cli-support.test.ts +82 -0
  341. package/src/components/cli-support.ts +16 -9
  342. package/src/components/driver.ts +129 -56
  343. package/src/components/fan-out-run.ts +56 -35
  344. package/src/components/pr-run.ts +30 -1
  345. package/src/components/promote-order.test.ts +90 -0
  346. package/src/components/promote-order.ts +75 -0
  347. package/src/components/promote.test.ts +10 -1
  348. package/src/components/promote.ts +26 -1
  349. package/src/components/release-compile.test.ts +147 -0
  350. package/src/components/release-compile.ts +445 -0
  351. package/src/components/release-gate.test.ts +215 -0
  352. package/src/components/release-gate.ts +341 -0
  353. package/src/components/release-promote.test.ts +159 -0
  354. package/src/components/release-promote.ts +232 -0
  355. package/src/components/run-progress.ts +7 -0
  356. package/src/components/wave-plan.ts +1 -1
  357. package/src/config.ts +7 -1
  358. package/src/index.ts +2 -0
  359. package/src/lifecycle/change-set-store.test.ts +217 -0
  360. package/src/lifecycle/change-set-store.ts +112 -0
  361. package/src/lifecycle/commit-sha.ts +23 -0
  362. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  363. package/src/lifecycle/gate-change-set.ts +133 -0
  364. package/src/lifecycle/gate-ledger.test.ts +33 -0
  365. package/src/lifecycle/gate-ledger.ts +66 -1
  366. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  367. package/src/lifecycle/release-cut.test.ts +411 -0
  368. package/src/lifecycle/release-cut.ts +367 -0
  369. package/src/lifecycle/release-ledger.test.ts +14 -0
  370. package/src/lifecycle/release-ledger.ts +85 -17
  371. package/src/lifecycle/release-store.ts +423 -0
  372. package/src/lifecycle/run-ledger.test.ts +10 -0
  373. package/src/lifecycle/run-ledger.ts +6 -0
  374. package/src/op/activities/change-coverage.ts +4 -1
  375. package/src/op/activities/source-release.ts +8 -3
  376. package/src/op/activities/source-rollback.ts +8 -4
  377. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  378. package/src/op/gate-name.ts +13 -3
  379. package/src/op/gate-release.test.ts +126 -0
  380. package/src/op/gate-run-locator.test.ts +110 -0
  381. package/src/op/gate-run-locator.ts +35 -0
  382. package/src/op/gate.ts +88 -6
  383. package/src/op/local-executor.ts +207 -52
  384. package/src/op/op-waves-run.ts +4 -0
  385. package/src/op/run-events.test.ts +166 -0
  386. package/src/op/run-live.ts +1 -1
  387. package/src/op/runtime.ts +39 -0
  388. package/src/op/runtimes/local.test.ts +16 -0
  389. package/src/op/runtimes/local.ts +9 -0
  390. package/src/point-decision.ts +12 -2
  391. package/src/run-event-store.test.ts +144 -0
  392. package/src/run-event-store.ts +213 -0
  393. package/src/run-event-stream.test.ts +175 -0
  394. package/src/run-event-stream.ts +306 -0
  395. package/src/run-event.test.ts +277 -0
  396. package/src/run-event.ts +451 -0
  397. package/src/wave-loop-golden.test.ts +9 -0
  398. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  399. package/src/workspace/ancestry-index.test.ts +93 -0
  400. package/src/workspace/change-set-show.schema.json +102 -0
  401. package/src/workspace/change-set-show.ts +72 -0
  402. package/src/workspace/change-set.schema.json +28 -1
  403. package/src/workspace/changes-cli.ts +10 -1
  404. package/src/workspace/changes.schema.json +135 -0
  405. package/src/workspace/changes.test.ts +1 -1
  406. package/src/workspace/changes.ts +84 -2
  407. package/src/workspace/ci-green-cli.ts +75 -13
  408. package/src/workspace/ci-green-forge.ts +24 -4
  409. package/src/workspace/ci-green-runs.test.ts +242 -0
  410. package/src/workspace/ci-green-signed.test.ts +302 -0
  411. package/src/workspace/ci-green-workflow.test.ts +16 -0
  412. package/src/workspace/ci-green-workflow.ts +12 -0
  413. package/src/workspace/ci-green.test.ts +131 -2
  414. package/src/workspace/ci-green.ts +624 -42
  415. package/src/workspace/ci-last-green.schema.json +32 -2
  416. package/src/workspace/ci-list.schema.json +525 -0
  417. package/src/workspace/commit-links.test.ts +279 -0
  418. package/src/workspace/commit-links.ts +183 -0
  419. package/src/workspace/declaration.schema.json +12 -0
  420. package/src/workspace/declaration.ts +22 -2
  421. package/src/workspace/identity-gate-rule.ts +91 -7
  422. package/src/workspace/identity.ts +14 -1
  423. package/src/workspace/intent.ts +48 -1
  424. package/src/workspace/ls.schema.json +3 -1
  425. package/src/workspace/ls.ts +6 -0
  426. package/src/workspace/member-pipeline.test.ts +17 -0
  427. package/src/workspace/member-pipeline.ts +9 -0
  428. package/src/workspace/promotion-order.ts +97 -0
  429. package/src/workspace/read-contract.e2e.test.ts +4 -1
  430. package/src/workspace/reason-codes.test.ts +2 -1
  431. package/src/workspace/reason-codes.ts +4 -1
  432. package/src/workspace/releases.schema.json +391 -0
  433. package/src/workspace/releases.test.ts +108 -0
  434. package/src/workspace/releases.ts +122 -0
  435. package/src/workspace/run-event.schema.json +288 -0
  436. package/src/workspace/status-contract.test.ts +52 -1
  437. package/src/workspace/status-gates.ts +56 -1
  438. package/src/workspace/status-stewards.ts +6 -0
  439. package/src/workspace/status.schema.json +70 -1
  440. package/src/workspace/status.ts +80 -1
  441. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  442. package/src/workspace/trust/op-run-statement.ts +148 -0
  443. package/src/workspace/trust/rotation.ts +61 -14
  444. package/src/workspace/trust/seal.ts +10 -0
  445. package/src/workspace/trust/ssh-commit.ts +10 -9
  446. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  447. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  448. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  449. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  450. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -0,0 +1,288 @@
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. The status sets are closed. 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.",
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": { "$ref": "#/$defs/settleStatus" }
160
+ }
161
+ }
162
+ ]
163
+ },
164
+ "phase-start": {
165
+ "allOf": [
166
+ { "$ref": "#/$defs/envelope" },
167
+ {
168
+ "type": "object",
169
+ "description": "A phase is about to run its steps: an Op's phase, a component composition's phase, or a pipeline's stage.",
170
+ "required": ["type", "component", "phase"],
171
+ "properties": {
172
+ "type": { "const": "phase-start" },
173
+ "component": { "$ref": "#/$defs/component" },
174
+ "phase": { "type": "string" }
175
+ }
176
+ }
177
+ ]
178
+ },
179
+ "phase-settle": {
180
+ "allOf": [
181
+ { "$ref": "#/$defs/envelope" },
182
+ {
183
+ "type": "object",
184
+ "description": "A phase settled: ok when every step in it settled ok, skipped or cached.",
185
+ "required": ["type", "component", "phase", "status"],
186
+ "properties": {
187
+ "type": { "const": "phase-settle" },
188
+ "component": { "$ref": "#/$defs/component" },
189
+ "phase": { "type": "string" },
190
+ "status": { "$ref": "#/$defs/settleStatus" }
191
+ }
192
+ }
193
+ ]
194
+ },
195
+ "step-start": {
196
+ "allOf": [
197
+ { "$ref": "#/$defs/envelope" },
198
+ {
199
+ "type": "object",
200
+ "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.",
201
+ "required": ["type", "component", "phase", "step"],
202
+ "properties": {
203
+ "type": { "const": "step-start" },
204
+ "component": { "$ref": "#/$defs/component" },
205
+ "phase": { "type": "string" },
206
+ "step": { "type": "string", "description": "The step's activity, gate:<name> for a gate, effect:<name> for an effect, or the job's name." },
207
+ "index": { "$ref": "#/$defs/stepIndex" }
208
+ }
209
+ }
210
+ ]
211
+ },
212
+ "step-settle": {
213
+ "allOf": [
214
+ { "$ref": "#/$defs/envelope" },
215
+ {
216
+ "type": "object",
217
+ "description": "A step settled.",
218
+ "required": ["type", "component", "phase", "step", "status", "durationMs"],
219
+ "properties": {
220
+ "type": { "const": "step-settle" },
221
+ "component": { "$ref": "#/$defs/component" },
222
+ "phase": { "type": "string" },
223
+ "step": { "type": "string" },
224
+ "index": { "$ref": "#/$defs/stepIndex" },
225
+ "status": {
226
+ "enum": ["ok", "failed", "skipped", "cached", "gated", "waiting"],
227
+ "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."
228
+ },
229
+ "skip": {
230
+ "enum": ["receipt", "resumed", "refused", "not-run", null],
231
+ "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."
232
+ },
233
+ "durationMs": { "type": ["integer", "null"], "minimum": 0, "description": "How long it ran, or null when the writer did not time it." },
234
+ "error": { "type": "string", "description": "With status failed, what failed." },
235
+ "message": { "type": "string", "description": "Why a step skipped, gated or waited, for a person." }
236
+ }
237
+ }
238
+ ]
239
+ },
240
+ "gate-waiting": {
241
+ "allOf": [
242
+ { "$ref": "#/$defs/envelope" },
243
+ {
244
+ "type": "object",
245
+ "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.",
246
+ "required": ["type", "component", "phase", "step", "op", "gate", "point"],
247
+ "properties": {
248
+ "type": { "const": "gate-waiting" },
249
+ "component": { "$ref": "#/$defs/component" },
250
+ "phase": { "type": "string" },
251
+ "step": { "type": "string" },
252
+ "index": { "$ref": "#/$defs/stepIndex" },
253
+ "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." },
254
+ "gate": { "type": ["string", "null"], "description": "The gate's name, or null when the step waits on a decision point alone." },
255
+ "point": { "type": ["string", "null"], "description": "The answer record id of the open question, which points answer takes, or null." },
256
+ "message": { "type": "string", "description": "Why the gate did not pass, such as an approval for a different plan." }
257
+ }
258
+ }
259
+ ]
260
+ },
261
+ "gate-resolved": {
262
+ "allOf": [
263
+ { "$ref": "#/$defs/envelope" },
264
+ {
265
+ "type": "object",
266
+ "description": "A gate the run reached was approved, so its step goes on.",
267
+ "required": ["type", "component", "phase", "step", "op", "gate", "resolvedBy", "resolvedAt", "via"],
268
+ "properties": {
269
+ "type": { "const": "gate-resolved" },
270
+ "component": { "$ref": "#/$defs/component" },
271
+ "phase": { "type": "string" },
272
+ "step": { "type": "string" },
273
+ "index": { "$ref": "#/$defs/stepIndex" },
274
+ "op": { "type": "string" },
275
+ "gate": { "type": "string" },
276
+ "resolvedBy": { "type": "string", "description": "Who approved it." },
277
+ "resolvedAt": { "type": "string", "description": "When the approval was recorded, ISO-8601." },
278
+ "via": {
279
+ "enum": ["quorum", "policy", "point", "alias", "empty", null],
280
+ "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."
281
+ },
282
+ "url": { "type": "string", "description": "Where the approval was made, when known." }
283
+ }
284
+ }
285
+ ]
286
+ }
287
+ }
288
+ }
@@ -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,32 @@ 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
+
455
481
  describe("gate state in chant workspace status --json (#2674)", () => {
456
482
  test("each member lists its gates with state, approvals, quorum and the approve line", async () => {
457
483
  const doc = result(await workspaceStatus({ cwd: gated(), env: "staging", compareTo: "prod", now: NOW }));
@@ -766,4 +792,29 @@ describe("stewards in chant workspace status --json (#2731)", () => {
766
792
  expect(doc.members[0].stewards.map((s) => s.name)).toEqual(["box-steward"]);
767
793
  expect(doc.members[0].stewardReasons.map((r) => r.code)).toEqual(["stewards-unreadable"]);
768
794
  });
795
+
796
+ test("a member lists its promotion order from the environments array (#4030)", async () => {
797
+ const root = stewarded();
798
+ writeFileSync(
799
+ join(root, "box", "chant.config.ts"),
800
+ 'export default { environments: ["dev", { name: "staging", canary: true }, "prod", "pr-*"] };\n',
801
+ );
802
+ const doc = result(await workspaceStatus({ cwd: root, env: "minimal" }));
803
+ expectValid(doc);
804
+ expect(doc.members[0].promotionOrder).toEqual([
805
+ { name: "dev", canary: false },
806
+ { name: "staging", canary: true },
807
+ { name: "prod", canary: false },
808
+ ]);
809
+ // A member with no chant.config.ts declares none.
810
+ expect(doc.members[1].promotionOrder).toEqual([]);
811
+ });
812
+
813
+ test("a promotion order the config can't give is null (#4030)", async () => {
814
+ const root = stewarded();
815
+ writeFileSync(join(root, "box", "chant.config.ts"), "throw new Error(\"broken\");\n");
816
+ const doc = result(await workspaceStatus({ cwd: root, env: "minimal" }));
817
+ expectValid(doc);
818
+ expect(doc.members[0].promotionOrder).toBeNull();
819
+ });
769
820
  });
@@ -25,6 +25,13 @@ import type { GateResolutionRecord, PendingGateRecord } from "../lifecycle/gate-
25
25
  import { isPendingGateExpired, latestPendingGate, latestResolutionForPlan, parseGateLedger } from "../lifecycle/gate-ledger";
26
26
  import { humanQuorumSince, tallyGateApprovals, type GateApprovalRule } from "../op/gate";
27
27
  import type { ReasonCode } from "./reason-codes";
28
+ import { CHANGE_SETS_DIR, changeSetFile } from "../lifecycle/change-set-store";
29
+
30
+ /** The stored change-set directory beside a gate ledger directory: `<prefix>_change-sets`. */
31
+ function changeSetDirFor(gatesDir: string): string {
32
+ const at = gatesDir.lastIndexOf("/");
33
+ return at < 0 ? CHANGE_SETS_DIR : `${gatesDir.slice(0, at)}/${CHANGE_SETS_DIR}`;
34
+ }
28
35
 
29
36
  /** Why a member's gates can't be listed. Closed: a new code is a contract change. */
30
37
  export const GATE_REASON_CODES = [
@@ -58,9 +65,27 @@ export interface StatusGate {
58
65
  env: string | null;
59
66
  /** The plan the run reached the gate with, or null for a gate that binds no plan. */
60
67
  planDigest: string | null;
68
+ /**
69
+ * The release the gate binds (#4045), from the pending fact: on a release
70
+ * gate, the one `chant components promote --release` passes, recorded
71
+ * under the op `release`. Absent on every other gate and on a fact written
72
+ * before chant 0.124.28.
73
+ */
74
+ releaseDigest?: string;
61
75
  state: GateState;
62
76
  /** When the run recorded the pending fact the state is read against. */
63
77
  recordedAt: string;
78
+ /** The commit the run reached the gate at (#4020), from the pending fact. Absent on a fact written before chant 0.124.28. */
79
+ gitSha?: string;
80
+ /** The run that reached the gate (#4020), from the pending fact: an Op run id, or the CI run id. Absent when the run had none. */
81
+ runId?: string;
82
+ /**
83
+ * The change set the gate binds (#4022): `planDigest`, when a change-set
84
+ * document is stored under it in the member's `_change-sets/`, read with
85
+ * `chant change-set show <digest>`. Absent when none is stored, as for a
86
+ * component gate, which binds a lifecycle-plan digest.
87
+ */
88
+ changeSet?: string;
64
89
  expiresAt: string;
65
90
  /** The approvals that count toward the gate for this plan, oldest first. */
66
91
  approvals: StatusGateApproval[];
@@ -113,6 +138,21 @@ function git(cwd: string, args: string[]): string {
113
138
  return execFileSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 64 * 1024 * 1024 });
114
139
  }
115
140
 
141
+ /**
142
+ * Lists the file names in a stored change-set directory on the branch at
143
+ * `commit`, or an empty set when it doesn't exist or can't be read: a gate
144
+ * then shows no link, and its state is read the same.
145
+ */
146
+ export type ChangeSetLister = (dir: string, commit: string, cwd: string) => Promise<Set<string>>;
147
+
148
+ export const defaultChangeSetLister: ChangeSetLister = async (dir, commit, cwd) => {
149
+ try {
150
+ return new Set(git(cwd, ["ls-tree", "--name-only", `${commit}:${dir}`]).split("\n").filter(Boolean));
151
+ } catch {
152
+ return new Set();
153
+ }
154
+ };
155
+
116
156
  export const defaultGateLedgerReader: GateLedgerReader = async (dir, commit, cwd) => {
117
157
  try {
118
158
  git(cwd, ["rev-parse", "--verify", "--quiet", `${commit}:${dir}`]);
@@ -143,7 +183,10 @@ const approvalOf = (r: GateResolutionRecord): StatusGateApproval => ({ principal
143
183
  function decide(component: string, standing: PendingGateRecord, facts: PendingGateRecord[], all: GateResolutionRecord[], now: string, rule: GateApprovalRule | null): StatusGate {
144
184
  const gate = standing.gate;
145
185
  // #3163: under identity.gates, an unsigned approval, or one from outside the class, doesn't count, as a run reads it.
146
- const resolutions = rule === null ? all : all.filter((r) => r.gate !== gate || rule.refuses(r) === null);
186
+ // #4045: nor does an approval for another release, and an `empty` pass is never an approval.
187
+ const resolutions = all
188
+ .filter((r) => r.gate !== gate || (r.empty !== true && r.releaseDigest === standing.releaseDigest))
189
+ .filter((r) => rule === null || r.gate !== gate || rule.refuses(r) === null);
147
190
  const env = standing.environment ?? null;
148
191
  const since = standing.timestamp;
149
192
  const planDigest = standing.planDigest;
@@ -177,8 +220,11 @@ function decide(component: string, standing: PendingGateRecord, facts: PendingGa
177
220
  name: gate,
178
221
  env,
179
222
  planDigest: planDigest ?? null,
223
+ ...(standing.releaseDigest !== undefined ? { releaseDigest: standing.releaseDigest } : {}),
180
224
  state,
181
225
  recordedAt: standing.timestamp,
226
+ ...(standing.gitSha ? { gitSha: standing.gitSha } : {}),
227
+ ...(standing.runId ? { runId: standing.runId } : {}),
182
228
  expiresAt: standing.expiresAt,
183
229
  approvals: approvals.map(approvalOf),
184
230
  needed,
@@ -226,6 +272,7 @@ export async function readMemberGates(
226
272
  now: string,
227
273
  read: GateLedgerReader = defaultGateLedgerReader,
228
274
  rules: GateRuleLookup = () => null,
275
+ listChangeSets: ChangeSetLister = defaultChangeSetLister,
229
276
  ): Promise<{ gates: StatusGate[]; ledger: StatusGateLedger }> {
230
277
  const ledger: StatusGateLedger = { layout, path: dir, shared: false, malformed: 0, reason: null };
231
278
  if (commit === null) {
@@ -247,5 +294,13 @@ export async function readMemberGates(
247
294
  gates.push(...read.gates);
248
295
  ledger.malformed += read.malformed;
249
296
  }
297
+ // #4022: link each gate to the change set stored under its plan digest,
298
+ // in the same member's `_change-sets/` beside `_gates/`.
299
+ if (gates.some((g) => g.planDigest !== null)) {
300
+ const stored = await listChangeSets(changeSetDirFor(dir), commit, cwd);
301
+ for (const g of gates) {
302
+ if (g.planDigest !== null && stored.has(changeSetFile(g.planDigest))) g.changeSet = g.planDigest;
303
+ }
304
+ }
250
305
  return { gates, ledger };
251
306
  }
@@ -78,6 +78,10 @@ export interface StatusStewardRun {
78
78
  status: OpRunRecord["status"];
79
79
  started: string;
80
80
  ended: string;
81
+ /** The commit the run built from (#4020). Absent on a record written before chant 0.124.28. */
82
+ gitSha?: string;
83
+ /** The CI run the Op ran in (#4020), when it ran in CI: the shape a release's `runOrigin` has. */
84
+ runOrigin?: OpRunRecord["runOrigin"];
81
85
  /**
82
86
  * The gate the run stopped at, for a `gated` run: its name, when the run
83
87
  * stopped there, the op it is recorded under (the Op's own, or the
@@ -411,6 +415,8 @@ export async function readMemberStewards(
411
415
  status: newest.status,
412
416
  started: newest.started,
413
417
  ended: newest.ended,
418
+ ...(newest.gitSha ? { gitSha: newest.gitSha } : {}),
419
+ ...(newest.runOrigin ? { runOrigin: newest.runOrigin } : {}),
414
420
  gate: newest.gate ? gateOf(newest.gate, op.name) : null,
415
421
  point: point?.wait ?? null,
416
422
  phases: newest.phases.map((p) => ({
@@ -228,6 +228,44 @@
228
228
  "message": { "type": "string" }
229
229
  }
230
230
  }
231
+ },
232
+ "promotionOrder": {
233
+ "description": "The member's promotion order (#4030): the literal entries of the environments array in its chant.config.ts, in order, with a pattern entry such as pr-* left out. A release moves from each step to the next, and components promote --to defaults --from to the step before. Empty when the config declares no environments, and null when it could not be read. canary is declared and shown only; no chant command acts on it yet. Optional: written from chant 0.124.28, absent from older output.",
234
+ "type": ["array", "null"],
235
+ "items": {
236
+ "type": "object",
237
+ "required": ["name", "canary"],
238
+ "properties": {
239
+ "name": { "type": "string" },
240
+ "canary": { "type": "boolean", "description": "True when the entry sets canary: true." }
241
+ }
242
+ }
243
+ },
244
+ "releaseCuts": {
245
+ "description": "The member's release cuts (#4026, ws-104), oldest first, from _releases/cuts.jsonl on the local chant/lifecycle, each with whether its signature verifies (#4031). Empty when none is cut, null when the cuts could not be read. Optional: written from chant 0.124.28, absent from older output.",
246
+ "type": ["array", "null"],
247
+ "items": {
248
+ "type": "object",
249
+ "required": ["releaseDigest", "label", "commit", "actor", "timestamp", "signature", "signatureCheck"],
250
+ "properties": {
251
+ "releaseDigest": { "type": "string", "pattern": "^(jcs1-)?sha256:[0-9a-f]{64}$" },
252
+ "label": { "type": ["string", "null"] },
253
+ "commit": { "type": ["string", "null"], "description": "The release's commit, or null when its release file can't be read." },
254
+ "actor": { "type": "string", "description": "Who cut it." },
255
+ "timestamp": { "type": "string", "description": "ISO 8601." },
256
+ "signature": { "type": ["string", "null"], "description": "The cut's armored ssh signature in the chant-release namespace over the release digest, the label (empty for none), the actor and the timestamp, joined by LF. Null for an unsigned cut." },
257
+ "signatureCheck": {
258
+ "type": "object",
259
+ "description": "Whether signature verifies for actor against the signers file at base and, when the declaration at base names a release gate in identity.gates with a class, whether .chant/trust.json at base puts actor in it. Judged on every read.",
260
+ "required": ["status", "key", "reason"],
261
+ "properties": {
262
+ "status": { "enum": ["verified", "unsigned", "unverified"] },
263
+ "key": { "type": ["string", "null"], "description": "The fingerprint of the key in the signature. Reported, never trusted." },
264
+ "reason": { "type": "string" }
265
+ }
266
+ }
267
+ }
268
+ }
231
269
  }
232
270
  }
233
271
  },
@@ -313,6 +351,17 @@
313
351
  "status": { "enum": ["ok", "fail", "gated", "waiting"], "description": "waiting: an activity asked a decision point that is still open (#2749), and the run stopped there." },
314
352
  "started": { "type": "string" },
315
353
  "ended": { "type": "string" },
354
+ "gitSha": { "type": "string", "description": "Added in contract 1 by #4020, written by chant 0.124.28 and newer. The commit the run built from. Absent on a record written by an older chant, and when the run knew no commit." },
355
+ "runOrigin": {
356
+ "type": "object",
357
+ "required": ["forge"],
358
+ "description": "Added in contract 1 by #4020, written by chant 0.124.28 and newer. The CI run the Op ran in, the shape a release's runOrigin has: the forge (github for GitHub and Forgejo, gitlab), the repo or project, and a link to the run. Absent outside CI.",
359
+ "properties": {
360
+ "forge": { "enum": ["github", "gitlab", "op", "local"] },
361
+ "repo": { "type": "string" },
362
+ "url": { "type": "string" }
363
+ }
364
+ },
316
365
  "gate": {
317
366
  "oneOf": [
318
367
  { "type": "null" },
@@ -828,6 +877,10 @@
828
877
  "description": "Read from the newest pending fact for the gate in env, as a run reads it. approved: the approvals recorded since it, for its plan, pass the gate (its quorum, or a policy permit in enforce mode). expired: not approved, and the pending fact is past expiresAt, so the next run records a fresh one. superseded: not approved, and an approval recorded since it names a different plan, so it doesn't count. pending: none of these."
829
878
  },
830
879
  "recordedAt": { "type": "string", "description": "When the run recorded the pending fact." },
880
+ "gitSha": { "type": "string", "description": "Added in contract 1 by #4020, written by chant 0.124.28 and newer. The commit the run reached the gate at, from the pending fact. Absent on a fact recorded by an older chant, and when the run knew no commit. It joins the gate to a release's gitSha and to a run's." },
881
+ "runId": { "type": "string", "description": "Added in contract 1 by #4020, written by chant 0.124.28 and newer. The run that reached the gate: an Op run id, or the CI run id for a component gate that ran in CI. Absent when the run had none." },
882
+ "changeSet": { "type": "string", "pattern": "^(?:jcs1-)?sha256:[0-9a-f]{64}$", "description": "Added in contract 1 by #4022, written by chant 0.124.28 and newer. The change set the gate binds: planDigest, when a change-set document is stored under it in the member's _change-sets/ on chant/lifecycle, as a pull-request gate's apply and a release gate (#4045) store it. chant change-set show <changeSet> --json prints it, each entry with its graph node. Absent when none is stored, as for a component gate, which binds a lifecycle-plan digest." },
883
+ "releaseDigest": { "type": "string", "pattern": "^(?:jcs1-)?sha256:[0-9a-f]{64}$", "description": "Added in contract 1 by #4045, written by chant 0.124.28 and newer. The release the gate binds, from the pending fact: set on a release gate, the gate chant components promote --release passes for an environment, whose component is the op release. An approval counts toward it only when it was recorded for this release, environment and planDigest. Absent on every other gate." },
831
884
  "expiresAt": { "type": "string" },
832
885
  "approvals": {
833
886
  "description": "The approvals that count toward the gate for its plan, oldest first.",
@@ -912,12 +965,28 @@
912
965
  "required": ["component", "digest", "gitSha", "inputDigest", "runId", "timestamp", "actor", "flags", "plan"],
913
966
  "properties": {
914
967
  "component": { "type": "string" },
915
- "digest": { "type": "string", "description": "The artifact digest the release deployed." },
968
+ "digest": { "type": "string", "description": "What the deploy record names, as its producer writes it: an artifact digest, a release-plan digest for a runner that records plans (_plans/<digest>.json, ws-055), or a product's own digest such as a schema digest (ws-104 question 3)." },
916
969
  "gitSha": { "type": "string", "description": "The commit the artifact was built from." },
917
970
  "inputDigest": { "type": ["string", "null"], "description": "The input-side digest when digest is a rendered one. Compare joins on it when present." },
918
971
  "runId": { "type": "string" },
919
972
  "timestamp": { "type": "string" },
920
973
  "actor": { "type": "string" },
974
+ "approver": { "type": ["string", "null"], "description": "Added in contract 1 by #4020, written by chant 0.124.28 and newer; the ledger has carried it since #1035. Who approved the change at its gate, or null for an ungated release." },
975
+ "admission": {
976
+ "description": "Added in contract 1 by #4020, written by chant 0.124.28 and newer. The gate resolution that admitted the release, or null for an ungated release and for one recorded by an older chant. With approver it names the line in the gate ledger.",
977
+ "oneOf": [
978
+ { "type": "null" },
979
+ {
980
+ "type": "object",
981
+ "required": ["op", "gate", "resolvedAt"],
982
+ "properties": {
983
+ "op": { "type": "string", "description": "The op, or for a component gate the component, whose gate ledger (_gates/<op>.jsonl) holds the resolution." },
984
+ "gate": { "type": "string", "description": "The gate's name." },
985
+ "resolvedAt": { "type": "string", "description": "When the resolution was recorded. The newest one, when several approvals counted." }
986
+ }
987
+ }
988
+ ]
989
+ },
921
990
  "flags": { "type": "array", "items": { "enum": ["legacy-digest"] } },
922
991
  "plan": {
923
992
  "type": ["object", "null"],