@intentius/chant 0.124.26 → 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 (459) 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/activity-registry.d.ts +6 -3
  155. package/dist/op/activity-registry.d.ts.map +1 -1
  156. package/dist/op/activity-registry.js +7 -5
  157. package/dist/op/discover.d.ts.map +1 -1
  158. package/dist/op/discover.js +14 -7
  159. package/dist/op/gate-name.d.ts +11 -2
  160. package/dist/op/gate-name.d.ts.map +1 -1
  161. package/dist/op/gate-name.js +12 -3
  162. package/dist/op/gate-run-locator.d.ts +22 -0
  163. package/dist/op/gate-run-locator.d.ts.map +1 -1
  164. package/dist/op/gate-run-locator.js +31 -0
  165. package/dist/op/gate.d.ts +27 -3
  166. package/dist/op/gate.d.ts.map +1 -1
  167. package/dist/op/gate.js +57 -3
  168. package/dist/op/local-executor.d.ts +36 -1
  169. package/dist/op/local-executor.d.ts.map +1 -1
  170. package/dist/op/local-executor.js +158 -52
  171. package/dist/op/op-waves-run.d.ts.map +1 -1
  172. package/dist/op/op-waves-run.js +4 -0
  173. package/dist/op/run-live.d.ts +2 -0
  174. package/dist/op/run-live.d.ts.map +1 -1
  175. package/dist/op/run-live.js +1 -1
  176. package/dist/op/runtime.d.ts +46 -0
  177. package/dist/op/runtime.d.ts.map +1 -1
  178. package/dist/op/runtimes/local.d.ts.map +1 -1
  179. package/dist/op/runtimes/local.js +12 -2
  180. package/dist/point-decision.d.ts +2 -0
  181. package/dist/point-decision.d.ts.map +1 -1
  182. package/dist/point-decision.js +9 -2
  183. package/dist/run-event-store.d.ts +77 -0
  184. package/dist/run-event-store.d.ts.map +1 -0
  185. package/dist/run-event-store.js +204 -0
  186. package/dist/run-event-stream.d.ts +132 -0
  187. package/dist/run-event-stream.d.ts.map +1 -0
  188. package/dist/run-event-stream.js +238 -0
  189. package/dist/run-event.d.ts +267 -0
  190. package/dist/run-event.d.ts.map +1 -0
  191. package/dist/run-event.js +207 -0
  192. package/dist/wave-runtime.mjs +225 -242
  193. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  194. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  195. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  196. package/dist/workspace/change-set-show.d.ts +56 -0
  197. package/dist/workspace/change-set-show.d.ts.map +1 -0
  198. package/dist/workspace/change-set-show.js +54 -0
  199. package/dist/workspace/change-set-show.schema.json +102 -0
  200. package/dist/workspace/change-set.schema.json +28 -1
  201. package/dist/workspace/changes-cli.d.ts.map +1 -1
  202. package/dist/workspace/changes-cli.js +10 -1
  203. package/dist/workspace/changes.d.ts +37 -0
  204. package/dist/workspace/changes.d.ts.map +1 -1
  205. package/dist/workspace/changes.js +49 -2
  206. package/dist/workspace/changes.schema.json +135 -0
  207. package/dist/workspace/ci-green-cli.d.ts +19 -4
  208. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  209. package/dist/workspace/ci-green-cli.js +75 -12
  210. package/dist/workspace/ci-green-forge.d.ts +20 -3
  211. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  212. package/dist/workspace/ci-green-forge.js +6 -4
  213. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  214. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  215. package/dist/workspace/ci-green-workflow.js +6 -0
  216. package/dist/workspace/ci-green.d.ts +273 -13
  217. package/dist/workspace/ci-green.d.ts.map +1 -1
  218. package/dist/workspace/ci-green.js +449 -38
  219. package/dist/workspace/ci-last-green.schema.json +32 -2
  220. package/dist/workspace/ci-list.schema.json +525 -0
  221. package/dist/workspace/commit-links.d.ts +85 -0
  222. package/dist/workspace/commit-links.d.ts.map +1 -0
  223. package/dist/workspace/commit-links.js +163 -0
  224. package/dist/workspace/declaration.d.ts +19 -0
  225. package/dist/workspace/declaration.d.ts.map +1 -1
  226. package/dist/workspace/declaration.js +1 -1
  227. package/dist/workspace/declaration.schema.json +12 -0
  228. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  229. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  230. package/dist/workspace/identity-gate-rule.js +72 -7
  231. package/dist/workspace/identity.d.ts +12 -0
  232. package/dist/workspace/identity.d.ts.map +1 -1
  233. package/dist/workspace/identity.js +13 -1
  234. package/dist/workspace/intent.d.ts +6 -1
  235. package/dist/workspace/intent.d.ts.map +1 -1
  236. package/dist/workspace/intent.js +51 -1
  237. package/dist/workspace/ls.d.ts +4 -0
  238. package/dist/workspace/ls.d.ts.map +1 -1
  239. package/dist/workspace/ls.js +2 -0
  240. package/dist/workspace/ls.schema.json +3 -1
  241. package/dist/workspace/member-pipeline.d.ts +6 -0
  242. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  243. package/dist/workspace/member-pipeline.js +6 -0
  244. package/dist/workspace/promotion-order.d.ts +41 -0
  245. package/dist/workspace/promotion-order.d.ts.map +1 -0
  246. package/dist/workspace/promotion-order.js +95 -0
  247. package/dist/workspace/reason-codes.d.ts +2 -0
  248. package/dist/workspace/reason-codes.d.ts.map +1 -1
  249. package/dist/workspace/reason-codes.js +4 -1
  250. package/dist/workspace/releases.d.ts +72 -0
  251. package/dist/workspace/releases.d.ts.map +1 -0
  252. package/dist/workspace/releases.js +94 -0
  253. package/dist/workspace/releases.schema.json +391 -0
  254. package/dist/workspace/run-event.schema.json +288 -0
  255. package/dist/workspace/status-gates.d.ts +26 -1
  256. package/dist/workspace/status-gates.d.ts.map +1 -1
  257. package/dist/workspace/status-gates.js +31 -2
  258. package/dist/workspace/status-stewards.d.ts +4 -0
  259. package/dist/workspace/status-stewards.d.ts.map +1 -1
  260. package/dist/workspace/status-stewards.js +2 -0
  261. package/dist/workspace/status.d.ts +39 -0
  262. package/dist/workspace/status.d.ts.map +1 -1
  263. package/dist/workspace/status.js +48 -1
  264. package/dist/workspace/status.schema.json +70 -1
  265. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  266. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  267. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  268. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  269. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  270. package/dist/workspace/trust/op-run-statement.js +143 -0
  271. package/dist/workspace/trust/rotation.d.ts +18 -2
  272. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  273. package/dist/workspace/trust/rotation.js +65 -16
  274. package/dist/workspace/trust/seal.d.ts +11 -0
  275. package/dist/workspace/trust/seal.d.ts.map +1 -1
  276. package/dist/workspace/trust/seal.js +9 -0
  277. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  278. package/dist/workspace/trust/ssh-commit.js +10 -10
  279. package/package.json +1 -1
  280. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  281. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  282. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  283. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  284. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  285. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  286. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  287. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  288. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  289. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  290. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  291. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  292. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  293. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  294. package/src/__golden__/wave-loop/op-spend.json +3 -0
  295. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  296. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  297. package/src/change-set-graph.test.ts +72 -0
  298. package/src/change-set-graph.ts +133 -0
  299. package/src/change-set.ts +76 -6
  300. package/src/chant-version.ts +16 -0
  301. package/src/cli/commands/build.ts +19 -8
  302. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  303. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  304. package/src/cli/commands/check-lexicon.ts +14 -2
  305. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  306. package/src/cli/commands/init-lexicon.test.ts +25 -0
  307. package/src/cli/commands/init-lexicon.ts +18 -8
  308. package/src/cli/compiled-cli.e2e.test.ts +31 -6
  309. package/src/cli/handlers/approve-release.test.ts +80 -0
  310. package/src/cli/handlers/approve.ts +18 -2
  311. package/src/cli/handlers/build.test.ts +52 -0
  312. package/src/cli/handlers/build.ts +29 -0
  313. package/src/cli/handlers/change-set.ts +49 -1
  314. package/src/cli/handlers/fan-out.test.ts +48 -0
  315. package/src/cli/handlers/fan-out.ts +15 -3
  316. package/src/cli/handlers/operator.test.ts +37 -0
  317. package/src/cli/handlers/pr.test.ts +194 -0
  318. package/src/cli/handlers/pr.ts +168 -34
  319. package/src/cli/handlers/promote.test.ts +389 -5
  320. package/src/cli/handlers/promote.ts +279 -20
  321. package/src/cli/handlers/release-implicit.e2e.test.ts +153 -0
  322. package/src/cli/handlers/release.e2e.test.ts +85 -0
  323. package/src/cli/handlers/release.ts +206 -0
  324. package/src/cli/handlers/run-auto-release.test.ts +71 -0
  325. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  326. package/src/cli/handlers/run.test.ts +30 -0
  327. package/src/cli/handlers/run.ts +176 -15
  328. package/src/cli/main.test.ts +7 -0
  329. package/src/cli/main.ts +88 -13
  330. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  331. package/src/cli/mcp/workspace-tools.ts +1 -1
  332. package/src/cli/parse-args.ts +37 -3
  333. package/src/cli/registry.ts +29 -3
  334. package/src/cli/run-events.ts +54 -0
  335. package/src/cli/version.ts +3 -13
  336. package/src/codegen/docs-in-site.test.ts +166 -0
  337. package/src/codegen/docs-in-site.ts +182 -0
  338. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  339. package/src/codegen/docs-types.ts +14 -1
  340. package/src/codegen/docs.ts +60 -7
  341. package/src/codegen/rehype-base-url.d.mts +3 -1
  342. package/src/codegen/rehype-base-url.mjs +15 -9
  343. package/src/codegen/rehype-base-url.test.ts +34 -0
  344. package/src/components/auto-release.test.ts +21 -0
  345. package/src/components/auto-release.ts +6 -1
  346. package/src/components/cli-support.test.ts +82 -0
  347. package/src/components/cli-support.ts +16 -9
  348. package/src/components/driver.ts +129 -56
  349. package/src/components/fan-out-run.ts +56 -35
  350. package/src/components/pr-run.ts +30 -1
  351. package/src/components/promote-order.test.ts +90 -0
  352. package/src/components/promote-order.ts +75 -0
  353. package/src/components/promote.test.ts +10 -1
  354. package/src/components/promote.ts +26 -1
  355. package/src/components/release-compile.test.ts +147 -0
  356. package/src/components/release-compile.ts +445 -0
  357. package/src/components/release-gate.test.ts +215 -0
  358. package/src/components/release-gate.ts +341 -0
  359. package/src/components/release-promote.test.ts +159 -0
  360. package/src/components/release-promote.ts +232 -0
  361. package/src/components/run-progress.ts +7 -0
  362. package/src/components/wave-plan.ts +1 -1
  363. package/src/config.ts +7 -1
  364. package/src/index.ts +2 -0
  365. package/src/lifecycle/change-set-store.test.ts +217 -0
  366. package/src/lifecycle/change-set-store.ts +112 -0
  367. package/src/lifecycle/commit-sha.ts +23 -0
  368. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  369. package/src/lifecycle/gate-change-set.ts +133 -0
  370. package/src/lifecycle/gate-ledger.test.ts +33 -0
  371. package/src/lifecycle/gate-ledger.ts +66 -1
  372. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  373. package/src/lifecycle/release-cut.test.ts +411 -0
  374. package/src/lifecycle/release-cut.ts +367 -0
  375. package/src/lifecycle/release-ledger.test.ts +14 -0
  376. package/src/lifecycle/release-ledger.ts +85 -17
  377. package/src/lifecycle/release-store.ts +423 -0
  378. package/src/lifecycle/run-ledger.test.ts +10 -0
  379. package/src/lifecycle/run-ledger.ts +6 -0
  380. package/src/op/activities/change-coverage.ts +4 -1
  381. package/src/op/activities/source-release.ts +8 -3
  382. package/src/op/activities/source-rollback.ts +8 -4
  383. package/src/op/activity-registry.ts +7 -5
  384. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  385. package/src/op/discover-root.test.ts +39 -0
  386. package/src/op/discover.ts +15 -8
  387. package/src/op/gate-name.ts +13 -3
  388. package/src/op/gate-release.test.ts +126 -0
  389. package/src/op/gate-run-locator.test.ts +110 -0
  390. package/src/op/gate-run-locator.ts +35 -0
  391. package/src/op/gate.ts +88 -6
  392. package/src/op/local-executor.ts +207 -52
  393. package/src/op/op-waves-run.ts +4 -0
  394. package/src/op/run-events.test.ts +166 -0
  395. package/src/op/run-live.ts +1 -1
  396. package/src/op/runtime.ts +39 -0
  397. package/src/op/runtimes/local.test.ts +16 -0
  398. package/src/op/runtimes/local.ts +13 -2
  399. package/src/point-decision.ts +12 -2
  400. package/src/run-event-store.test.ts +144 -0
  401. package/src/run-event-store.ts +213 -0
  402. package/src/run-event-stream.test.ts +175 -0
  403. package/src/run-event-stream.ts +306 -0
  404. package/src/run-event.test.ts +277 -0
  405. package/src/run-event.ts +451 -0
  406. package/src/wave-loop-golden.test.ts +9 -0
  407. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  408. package/src/workspace/ancestry-index.test.ts +93 -0
  409. package/src/workspace/change-set-show.schema.json +102 -0
  410. package/src/workspace/change-set-show.ts +72 -0
  411. package/src/workspace/change-set.schema.json +28 -1
  412. package/src/workspace/changes-cli.ts +10 -1
  413. package/src/workspace/changes.schema.json +135 -0
  414. package/src/workspace/changes.test.ts +1 -1
  415. package/src/workspace/changes.ts +84 -2
  416. package/src/workspace/ci-green-cli.ts +75 -13
  417. package/src/workspace/ci-green-forge.ts +24 -4
  418. package/src/workspace/ci-green-runs.test.ts +242 -0
  419. package/src/workspace/ci-green-signed.test.ts +302 -0
  420. package/src/workspace/ci-green-workflow.test.ts +16 -0
  421. package/src/workspace/ci-green-workflow.ts +12 -0
  422. package/src/workspace/ci-green.test.ts +131 -2
  423. package/src/workspace/ci-green.ts +624 -42
  424. package/src/workspace/ci-last-green.schema.json +32 -2
  425. package/src/workspace/ci-list.schema.json +525 -0
  426. package/src/workspace/commit-links.test.ts +279 -0
  427. package/src/workspace/commit-links.ts +183 -0
  428. package/src/workspace/declaration.schema.json +12 -0
  429. package/src/workspace/declaration.ts +22 -2
  430. package/src/workspace/identity-gate-rule.ts +91 -7
  431. package/src/workspace/identity.ts +14 -1
  432. package/src/workspace/intent.ts +48 -1
  433. package/src/workspace/ls.schema.json +3 -1
  434. package/src/workspace/ls.ts +6 -0
  435. package/src/workspace/member-pipeline.test.ts +17 -0
  436. package/src/workspace/member-pipeline.ts +9 -0
  437. package/src/workspace/promotion-order.ts +97 -0
  438. package/src/workspace/read-contract.e2e.test.ts +4 -1
  439. package/src/workspace/reason-codes.test.ts +2 -1
  440. package/src/workspace/reason-codes.ts +4 -1
  441. package/src/workspace/releases.schema.json +391 -0
  442. package/src/workspace/releases.test.ts +108 -0
  443. package/src/workspace/releases.ts +122 -0
  444. package/src/workspace/run-event.schema.json +288 -0
  445. package/src/workspace/status-contract.test.ts +52 -1
  446. package/src/workspace/status-gates.ts +56 -1
  447. package/src/workspace/status-stewards.ts +6 -0
  448. package/src/workspace/status.schema.json +70 -1
  449. package/src/workspace/status.ts +80 -1
  450. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  451. package/src/workspace/trust/op-run-statement.ts +148 -0
  452. package/src/workspace/trust/rotation.ts +61 -14
  453. package/src/workspace/trust/seal.ts +10 -0
  454. package/src/workspace/trust/ssh-commit.ts +10 -9
  455. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  456. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  457. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  458. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  459. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -54,7 +54,11 @@ import { RunWorkLease, LEASE_LOST, workLeaseProblems, workLeaseNeedsRunItem, typ
54
54
  import { WORK_LEASE_STEP_ID } from "./types";
55
55
  import { LiveRun, currentLiveRun, withLiveRun } from "./run-live";
56
56
  import { runEnvOf } from "../lifecycle/run-ledger";
57
- import { getHeadCommit } from "../lifecycle/git";
57
+ import { resolveCommitSha } from "../lifecycle/commit-sha";
58
+ import { resolveCiRun, type RunOrigin } from "../lifecycle/release-ledger";
59
+ import { runEventsOfStepRecord, type RunEventRuntime, type RunEventSkipReason } from "../run-event";
60
+ import { openRunEventStore, teeLines } from "../run-event-store";
61
+ import { openRunEventStream, runEventRunOf, type RunEventSink, type RunEventStream } from "../run-event-stream";
58
62
  import { describePlanDigest, samePlanDigest } from "../lifecycle/plan-digest";
59
63
  import type { GateCheckpoint } from "../lifecycle/gate-checkpoint";
60
64
  import type { ActivityContract } from "./activity-contract";
@@ -64,6 +68,16 @@ export { parseDuration } from "./duration";
64
68
 
65
69
  // ── Records ─────────────────────────────────────────────────────────────────
66
70
 
71
+ /**
72
+ * One settled step of an Op run, as `OpRunResult.records` holds it.
73
+ *
74
+ * As a `chant run <op> --progress-json` line it is deprecated: read
75
+ * `RunEvent` (`../run-event.ts`, exported from `@intentius/chant`)
76
+ * instead, whose `step-settle` carries the run id, commit and runtime too,
77
+ * and whose step statuses are `ok`, `failed`, `skipped`, `cached`, `gated`
78
+ * and `waiting` (#4019). `runEventsOfStepRecord` maps a record onto it.
79
+ * `--progress-json` keeps printing records until #4041 moves it.
80
+ */
67
81
  export interface StepRecord {
68
82
  phase: string;
69
83
  fn: string;
@@ -94,7 +108,7 @@ export interface StepRecord {
94
108
  * On a gate with an `approval` block (#2508): whether a quorum or a policy permit passed it. `"point"` on a gate that asks a decision point (#3170).
95
109
  * `"alias"` (#3949) when an approval recorded under one of the gate's aliases counted, on any gate.
96
110
  */
97
- via?: "quorum" | "policy" | "point" | "alias";
111
+ via?: "quorum" | "policy" | "point" | "alias" | "empty";
98
112
  /** On a gate with a quorum (#2508): every approver who counted toward it. */
99
113
  approvers?: string[];
100
114
  /** On a gate that asks a decision point (#3170): the answer record that passed it, which the gate ledger's resolution cites too. */
@@ -557,9 +571,29 @@ function receiptReadStep(step: EffectStep, outputs?: string): ActivityStep {
557
571
  };
558
572
  }
559
573
 
560
- /** A skipped-record for a step that will not run. */
561
- function skippedRecord(phaseName: string, fn: string, args?: Record<string, unknown>): StepRecord {
562
- return { phase: phaseName, fn, args: args ?? {}, status: "skipped", durationMs: 0 };
574
+ /**
575
+ * What the run-event stream (#4041) says about a record that the record
576
+ * itself does not: the step's place in its phase, why a bare skipped record
577
+ * skipped, and the gate a gate step stopped at. Kept beside the record
578
+ * rather than on it, so `--json`, the run ledger and `--progress-json` print
579
+ * what they printed before.
580
+ */
581
+ interface RecordHint {
582
+ index?: number;
583
+ skip?: RunEventSkipReason;
584
+ gate?: { op: string; gate: string };
585
+ }
586
+ const recordHints = new WeakMap<StepRecord, RecordHint>();
587
+
588
+ /** Note `hint` against `record` for its run events, and hand the record back. */
589
+ function hinted(record: StepRecord, hint: RecordHint): StepRecord {
590
+ recordHints.set(record, { ...recordHints.get(record), ...hint });
591
+ return record;
592
+ }
593
+
594
+ /** A skipped-record for a step that will not run: one an earlier failure or stop left, unless `skip` says otherwise. */
595
+ function skippedRecord(phaseName: string, fn: string, args?: Record<string, unknown>, skip: RunEventSkipReason = "not-run"): StepRecord {
596
+ return hinted({ phase: phaseName, fn, args: args ?? {}, status: "skipped", durationMs: 0 }, { skip });
563
597
  }
564
598
 
565
599
  // ── Gate steps (#2119) ───────────────────────────────────────────────────────
@@ -570,8 +604,12 @@ interface GateContext {
570
604
  port: GateLedgerPort;
571
605
  now?: string;
572
606
  runId?: string;
607
+ /** The commit the run is at (#4020), recorded on the gate facts it writes and on the run record. */
608
+ gitSha?: string;
573
609
  /** Called once per settled step, in production order (#2121) — what `--progress-json` streams from. */
574
610
  onRecord?: (record: StepRecord) => void;
611
+ /** Where the run's events go (#4041): a step's start, its settle and its gate's decision. */
612
+ emit?: RunEventSink;
575
613
  /** The run context's list of passed gates (#2522). A gate that passes is appended here. */
576
614
  passed?: PassedGate[];
577
615
  /** The run's hold on its work item's lease (#2748). Absent in compensation phases, which run whatever the lease did. */
@@ -608,6 +646,7 @@ async function workBoundary(
608
646
  if (!work) return undefined;
609
647
  if (work.due(resultsById)) {
610
648
  const start = Date.now();
649
+ startStep(ctx, phaseName, "workLease:claim");
611
650
  let claimed: Awaited<ReturnType<RunWorkLease["claim"]>>;
612
651
  try {
613
652
  claimed = await work.claim(resultsById);
@@ -656,7 +695,25 @@ async function workBoundary(
656
695
  /** Collect records and hand each to the caller's progress sink in one move. */
657
696
  function pushRecord(sink: StepRecord[], ctx: GateContext, ...recs: StepRecord[]): void {
658
697
  sink.push(...recs);
659
- for (const r of recs) ctx.onRecord?.(r);
698
+ for (const r of recs) announce(ctx, r);
699
+ }
700
+
701
+ /** Hand a settled record to the progress sink and its run events to the event sink. */
702
+ function announce(ctx: GateContext, record: StepRecord): void {
703
+ ctx.onRecord?.(record);
704
+ if (!ctx.emit) return;
705
+ const hint = recordHints.get(record);
706
+ for (const body of runEventsOfStepRecord(record, { op: ctx.op, ...hint })) ctx.emit(body);
707
+ }
708
+
709
+ /** A step is about to run: its `step-start` event (#4041). */
710
+ function startStep(ctx: GateContext, phase: string, step: string, index?: number): void {
711
+ ctx.emit?.({ type: "step-start", component: null, phase, step, ...(index !== undefined ? { index } : {}) });
712
+ }
713
+
714
+ /** A gate step's record, hinted with its place and, when nobody has approved it, the gate it stopped at. */
715
+ function gateRecordOf(ran: { record: StepRecord; pending?: PendingGateRecord }, index: number): StepRecord {
716
+ return hinted(ran.record, { index, ...(ran.pending ? { gate: { op: ran.pending.op, gate: ran.pending.gate } } : {}) });
660
717
  }
661
718
 
662
719
  /** The record name a gate step lands under, so a reader (and a JSON consumer) can pick it out of `records`. */
@@ -746,6 +803,7 @@ async function runGateStep(
746
803
  ...(step.description ? { description: step.description } : {}),
747
804
  ...(step.timeout ? { timeout: step.timeout } : {}),
748
805
  ...(gates.runId ? { runId: gates.runId } : {}),
806
+ ...(gates.gitSha ? { gitSha: gates.gitSha } : {}),
749
807
  ...(planDigest !== undefined ? { planDigest } : {}),
750
808
  ...(approval ? { approval } : {}),
751
809
  ...(gates.now ? { now: gates.now } : {}),
@@ -848,6 +906,7 @@ async function runPointGateStep(
848
906
  ...(step.description ? { description: step.description } : {}),
849
907
  ...(step.timeout ? { timeout: step.timeout } : {}),
850
908
  ...(gates.runId ? { runId: gates.runId } : {}),
909
+ ...(gates.gitSha ? { gitSha: gates.gitSha } : {}),
851
910
  ...(planDigest !== undefined ? { planDigest } : {}),
852
911
  ...(gates.env !== undefined ? { env: gates.env } : {}),
853
912
  ...(gates.now ? { now: gates.now } : {}),
@@ -891,7 +950,8 @@ async function runEffectStep(
891
950
  profiles: Record<string, ActivityProfile>,
892
951
  resultsById: Map<string, unknown>,
893
952
  gates: GateContext,
894
- signal?: AbortSignal,
953
+ signal: AbortSignal | undefined,
954
+ index: number,
895
955
  ): Promise<{
896
956
  records: StepRecord[];
897
957
  failed: boolean;
@@ -907,23 +967,26 @@ async function runEffectStep(
907
967
  outputs = effectOutputsDigest(step, resultsById);
908
968
  } catch (err) {
909
969
  // A resolved value JSON cannot hold (canonicalJson refuses it) would be a wrong expectation.
910
- pushRecord(records, gates, { phase: phaseName, fn: `effect:${step.receipt.name}`, args: {}, status: "fail", durationMs: 0, error: errMessage(err) });
970
+ startStep(gates, phaseName, `effect:${step.receipt.name}`, index);
971
+ pushRecord(records, gates, hinted({ phase: phaseName, fn: `effect:${step.receipt.name}`, args: {}, status: "fail", durationMs: 0, error: errMessage(err) }, { index }));
911
972
  return { records, failed: true };
912
973
  }
974
+ startStep(gates, phaseName, "receiptRead", index);
913
975
  const read = await runStep(receiptReadStep(step, outputs), phaseName, activities, profiles, resultsById, signal);
914
- pushRecord(records, gates, read.record);
976
+ pushRecord(records, gates, hinted(read.record, { index }));
915
977
  if (read.record.status === "fail") return { records, failed: true };
916
978
 
917
979
  const result = read.result as Partial<ReceiptReadResult> | undefined;
918
980
  if (typeof result?.expectation !== "string") {
919
- pushRecord(records, gates, {
981
+ startStep(gates, phaseName, `effect:${step.receipt.name}`, index);
982
+ pushRecord(records, gates, hinted({
920
983
  phase: phaseName,
921
984
  fn: `effect:${step.receipt.name}`,
922
985
  args: {},
923
986
  status: "fail",
924
987
  durationMs: 0,
925
988
  error: "receiptRead returned no expectation — the receipt store activity must return { current, expectation }",
926
- });
989
+ }, { index }));
927
990
  return { records, failed: true };
928
991
  }
929
992
  const expectation = result.expectation;
@@ -932,9 +995,9 @@ async function runEffectStep(
932
995
  // Effect already applied — skip the nested steps, write nothing.
933
996
  for (const nested of step.steps) {
934
997
  pushRecord(records, gates,
935
- nested.kind === "activity"
936
- ? skippedRecord(phaseName, nested.fn, nested.args)
937
- : skippedRecord(phaseName, gateFn(nested)),
998
+ hinted(nested.kind === "activity"
999
+ ? skippedRecord(phaseName, nested.fn, nested.args, "receipt")
1000
+ : skippedRecord(phaseName, gateFn(nested), undefined, "receipt"), { index }),
938
1001
  );
939
1002
  }
940
1003
  return { records, failed: false };
@@ -943,19 +1006,21 @@ async function runEffectStep(
943
1006
  const skipRest = (from: number) => {
944
1007
  for (const skipped of step.steps.slice(from)) {
945
1008
  pushRecord(records, gates,
946
- skipped.kind === "activity"
1009
+ hinted(skipped.kind === "activity"
947
1010
  ? skippedRecord(phaseName, skipped.fn, skipped.args)
948
- : skippedRecord(phaseName, gateFn(skipped)),
1011
+ : skippedRecord(phaseName, gateFn(skipped)), { index }),
949
1012
  );
950
1013
  }
951
- pushRecord(records, gates, skippedRecord(phaseName, "receiptWrite"));
1014
+ pushRecord(records, gates, hinted(skippedRecord(phaseName, "receiptWrite"), { index }));
952
1015
  };
953
1016
 
954
1017
  for (let i = 0; i < step.steps.length; i++) {
955
1018
  const nested = step.steps[i];
956
1019
  if (isGate(nested)) {
957
- const { record, pending, pushed, pushWarning } = await runGateStep(nested, phaseName, gates, resultsById);
958
- pushRecord(records, gates, record);
1020
+ startStep(gates, phaseName, gateFn(nested), index);
1021
+ const decided = await runGateStep(nested, phaseName, gates, resultsById);
1022
+ const { record, pending, pushed, pushWarning } = decided;
1023
+ pushRecord(records, gates, gateRecordOf(decided, index));
959
1024
  if (record.status === "fail") {
960
1025
  // Receipt left untouched, as for any other failing nested step
961
1026
  // (#2301) — the effect is not applied and the next run re-proposes it.
@@ -975,8 +1040,9 @@ async function runEffectStep(
975
1040
  }
976
1041
  continue;
977
1042
  }
1043
+ startStep(gates, phaseName, nested.fn, index);
978
1044
  const ran = await runStep(nested, phaseName, activities, profiles, resultsById, signal);
979
- pushRecord(records, gates, ran.record);
1045
+ pushRecord(records, gates, hinted(ran.record, { index }));
980
1046
  if (ran.pending) {
981
1047
  // A command's own gate (#2779): the receipt is left untouched, as for a
982
1048
  // pending gate step.
@@ -997,6 +1063,7 @@ async function runEffectStep(
997
1063
  }
998
1064
 
999
1065
  // Sole writer of the receipt: on success of every nested step, last.
1066
+ startStep(gates, phaseName, "receiptWrite", index);
1000
1067
  const wrote = await runStep(
1001
1068
  {
1002
1069
  kind: "activity",
@@ -1010,7 +1077,7 @@ async function runEffectStep(
1010
1077
  resultsById,
1011
1078
  signal,
1012
1079
  );
1013
- pushRecord(records, gates, wrote.record);
1080
+ pushRecord(records, gates, hinted(wrote.record, { index }));
1014
1081
  return { records, failed: wrote.record.status === "fail" };
1015
1082
  }
1016
1083
 
@@ -1041,44 +1108,48 @@ async function runPhase(
1041
1108
  // gate is about to strand would defeat the point of stopping at it.
1042
1109
  const gateRecords: StepRecord[] = [];
1043
1110
  const stop = await workBoundary(phase.name, gates, resultsById, gateRecords);
1111
+ const at = (step: StepDefinition) => phase.steps.indexOf(step);
1112
+ const skipActivities = () => {
1113
+ for (const skipped of phase.steps.filter(isActivity)) {
1114
+ pushRecord(gateRecords, gates, hinted(skippedRecord(phase.name, skipped.fn, skipped.args), { index: at(skipped) }));
1115
+ }
1116
+ };
1044
1117
  if (stop) {
1045
1118
  for (const skipped of phase.steps) {
1046
- pushRecord(gateRecords, gates, isGate(skipped) ? skippedRecord(phase.name, gateFn(skipped)) : skippedRecord(phase.name, (skipped as ActivityStep).fn, (skipped as ActivityStep).args));
1119
+ pushRecord(gateRecords, gates, hinted(isGate(skipped) ? skippedRecord(phase.name, gateFn(skipped)) : skippedRecord(phase.name, (skipped as ActivityStep).fn, (skipped as ActivityStep).args), { index: at(skipped) }));
1047
1120
  }
1048
1121
  throw new WorkStop(gateRecords, phase.name, stop === "lost");
1049
1122
  }
1050
1123
  for (const step of phase.steps.filter(isGate)) {
1051
- const { record, pending, pushed, pushWarning } = await runGateStep(step, phase.name, gates, resultsById);
1052
- pushRecord(gateRecords, gates, record);
1124
+ startStep(gates, phase.name, gateFn(step), at(step));
1125
+ const decided = await runGateStep(step, phase.name, gates, resultsById);
1126
+ const { record, pending, pushed, pushWarning } = decided;
1127
+ pushRecord(gateRecords, gates, gateRecordOf(decided, at(step)));
1053
1128
  if (record.status === "fail") {
1054
1129
  // The gate could not be decided at all (#2301). Same treatment as a
1055
1130
  // pending one: nothing fans out, the activities are recorded skipped.
1056
- for (const skipped of phase.steps.filter(isActivity)) {
1057
- pushRecord(gateRecords, gates, skippedRecord(phase.name, skipped.fn, skipped.args));
1058
- }
1131
+ skipActivities();
1059
1132
  throw new PhaseFailure(gateRecords);
1060
1133
  }
1061
1134
  if (pending) {
1062
- for (const skipped of phase.steps.filter(isActivity)) {
1063
- pushRecord(gateRecords, gates, skippedRecord(phase.name, skipped.fn, skipped.args));
1064
- }
1135
+ skipActivities();
1065
1136
  throw new GateStop(gateRecords, pending, phase.name, pushed, pushWarning);
1066
1137
  }
1067
1138
  if (record.point) {
1068
1139
  // A gate's open decision point (#3170): nothing fans out.
1069
- for (const skipped of phase.steps.filter(isActivity)) {
1070
- pushRecord(gateRecords, gates, skippedRecord(phase.name, skipped.fn, skipped.args));
1071
- }
1140
+ skipActivities();
1072
1141
  throw new PointStop(gateRecords, record.point, phase.name);
1073
1142
  }
1074
1143
  }
1075
1144
  const steps = phase.steps.filter(isActivity);
1076
1145
  const settled = await Promise.all(steps.map((s) => {
1077
1146
  const source = gates.compensating ? unfinishedSource(s, resultsById) : undefined;
1078
- return source ? { record: unfinishedSourceRecord(s, phase.name, source) } as RanStep : runStep(s, phase.name, activities, profiles, resultsById, signal);
1147
+ if (source) return { record: unfinishedSourceRecord(s, phase.name, source) } as RanStep;
1148
+ startStep(gates, phase.name, s.fn, at(s));
1149
+ return runStep(s, phase.name, activities, profiles, resultsById, signal);
1079
1150
  }));
1080
- const ran = settled.map((r) => r.record);
1081
- for (const r of ran) gates.onRecord?.(r);
1151
+ const ran = settled.map((r, k) => hinted(r.record, { index: at(steps[k]!) }));
1152
+ for (const r of ran) announce(gates, r);
1082
1153
  const records = gateRecords.concat(ran);
1083
1154
  if (records.some((r) => r.status === "fail")) throw new PhaseFailure(records);
1084
1155
  // A step whose command stopped at its own gate (#2779) stops the run at
@@ -1096,13 +1167,14 @@ async function runPhase(
1096
1167
  const records: StepRecord[] = [];
1097
1168
 
1098
1169
  const skipRemaining = (from: number) => {
1099
- for (const skipped of steps.slice(from)) {
1170
+ for (const [k, skipped] of steps.slice(from).entries()) {
1171
+ const index = { index: from + k };
1100
1172
  if (isEffect(skipped)) {
1101
- pushRecord(records, gates, skippedRecord(phase.name, `effect:${skipped.receipt.name}`));
1173
+ pushRecord(records, gates, hinted(skippedRecord(phase.name, `effect:${skipped.receipt.name}`), index));
1102
1174
  } else if (isGate(skipped)) {
1103
- pushRecord(records, gates, skippedRecord(phase.name, gateFn(skipped)));
1175
+ pushRecord(records, gates, hinted(skippedRecord(phase.name, gateFn(skipped)), index));
1104
1176
  } else {
1105
- pushRecord(records, gates, skippedRecord(phase.name, skipped.fn, skipped.args));
1177
+ pushRecord(records, gates, hinted(skippedRecord(phase.name, skipped.fn, skipped.args), index));
1106
1178
  }
1107
1179
  }
1108
1180
  };
@@ -1117,8 +1189,10 @@ async function runPhase(
1117
1189
  throw new WorkStop(records, phase.name, stop === "lost");
1118
1190
  }
1119
1191
  if (isGate(step)) {
1120
- const { record, pending, pushed, pushWarning } = await runGateStep(step, phase.name, gates, resultsById);
1121
- pushRecord(records, gates, record);
1192
+ startStep(gates, phase.name, gateFn(step), i);
1193
+ const decided = await runGateStep(step, phase.name, gates, resultsById);
1194
+ const { record, pending, pushed, pushWarning } = decided;
1195
+ pushRecord(records, gates, gateRecordOf(decided, i));
1122
1196
  if (record.status === "fail") {
1123
1197
  // A gate that could not be decided is a failed step, not a pending
1124
1198
  // one (#2301) — the run failed, and the record says why.
@@ -1139,12 +1213,12 @@ async function runPhase(
1139
1213
  }
1140
1214
  const source = gates.compensating ? unfinishedSource(step, resultsById) : undefined;
1141
1215
  if (source) {
1142
- pushRecord(records, gates, unfinishedSourceRecord(step, phase.name, source));
1216
+ pushRecord(records, gates, hinted(unfinishedSourceRecord(step, phase.name, source), { index: i }));
1143
1217
  continue;
1144
1218
  }
1145
1219
  if (isEffect(step)) {
1146
1220
  const { records: effRecords, failed, pending, pushed, pushWarning, point } = await runEffectStep(
1147
- step, phase.name, activities, profiles, resultsById, gates, signal,
1221
+ step, phase.name, activities, profiles, resultsById, gates, signal, i,
1148
1222
  );
1149
1223
  records.push(...effRecords); // already emitted by runEffectStep
1150
1224
  if (point) {
@@ -1161,8 +1235,9 @@ async function runPhase(
1161
1235
  }
1162
1236
  continue;
1163
1237
  }
1238
+ startStep(gates, phase.name, step.fn, i);
1164
1239
  const { record, pending } = await runStep(step, phase.name, activities, profiles, resultsById, signal);
1165
- pushRecord(records, gates, record);
1240
+ pushRecord(records, gates, hinted(record, { index: i }));
1166
1241
  if (pending) {
1167
1242
  // The step's command stopped at its own gate (#2779): the run ends
1168
1243
  // `gated` there, as at a pending gate step.
@@ -1216,6 +1291,15 @@ export interface RunOpOptions {
1216
1291
  * `--progress-json` from. Side-effect free when omitted.
1217
1292
  */
1218
1293
  onRecord?: (record: StepRecord) => void;
1294
+ /**
1295
+ * Where the run's event stream goes (#4041, `chant run <op> --events-json`),
1296
+ * one `run-event.schema.json` line at a time: the run's start and settle,
1297
+ * each phase's, each step's start and settle, and each gate's decision.
1298
+ * `runtime` and `runOrigin` name where the run happened when the executor
1299
+ * cannot tell: by default it is `ci` in a CI job, `steward` in a steward's
1300
+ * turn and `local` otherwise. Omitted, nothing is written.
1301
+ */
1302
+ runEvents?: { write: (line: string) => void; runtime?: RunEventRuntime; runOrigin?: RunOrigin };
1219
1303
  /**
1220
1304
  * Append the run's {@link OpRunRecord} to the run ledger
1221
1305
  * (`../lifecycle/run-ledger.ts`) when the run settles (#2118). Opt-in on
@@ -1225,6 +1309,16 @@ export interface RunOpOptions {
1225
1309
  * `chant operator` pass it; nothing else does.
1226
1310
  */
1227
1311
  ledger?: { cwd?: string };
1312
+ /**
1313
+ * Signs the run's record when it settles (#4044): given the record without
1314
+ * a statement, returns the DSSE envelope stored on it as `statement`
1315
+ * (`../workspace/trust/op-run-statement.ts`). `chant run <op> --key` and
1316
+ * `CHANT_RUNNER_KEY` pass one made from a runner key. When it throws, the
1317
+ * record is written unsigned and `onSignError` hears why.
1318
+ */
1319
+ signRecord?: (record: OpRunRecord) => NonNullable<OpRunRecord["statement"]>;
1320
+ /** Called when {@link signRecord} throws. The run's outcome stands. */
1321
+ onSignError?: (err: unknown) => void;
1228
1322
  /**
1229
1323
  * Called when the ledger append itself fails. The run's own outcome is
1230
1324
  * already decided by then, so a ledger failure is reported to the caller,
@@ -1320,6 +1414,9 @@ export async function runOpLocally(
1320
1414
  ...(steward ? { steward } : {}),
1321
1415
  })
1322
1416
  : undefined;
1417
+ // The run's event stream (#4041), opened once the commit is known; held
1418
+ // here so a run that throws before it settles still ends its stream.
1419
+ const events: { stream?: RunEventStream } = {};
1323
1420
  try {
1324
1421
  const stepConsole = options.stepOutput
1325
1422
  ? { write: options.stepOutput, ...(options.stepHeartbeatMs !== undefined ? { heartbeatMs: options.stepHeartbeatMs } : {}) }
@@ -1327,10 +1424,13 @@ export async function runOpLocally(
1327
1424
  return await withLiveRun(live, () =>
1328
1425
  withRunConsole(stepConsole, () =>
1329
1426
  withOpRunContext(run, () =>
1330
- runOpInTurn(config, activities, profiles, signal, { ...options, runId, ...(steward ? { steward } : {}) }, run),
1427
+ runOpInTurn(config, activities, profiles, signal, { ...options, runId, ...(steward ? { steward } : {}) }, run, events),
1331
1428
  ),
1332
1429
  ),
1333
1430
  );
1431
+ } catch (err) {
1432
+ events.stream?.settle("failed", errMessage(err));
1433
+ throw err;
1334
1434
  } finally {
1335
1435
  live?.close();
1336
1436
  restore?.();
@@ -1344,15 +1444,49 @@ async function runOpInTurn(
1344
1444
  signal: AbortSignal | undefined,
1345
1445
  options: RunOpOptions & { runId: string },
1346
1446
  run: OpRunContext,
1447
+ events: { stream?: RunEventStream },
1347
1448
  ): Promise<OpRunResult> {
1348
1449
  const runId = options.runId;
1450
+ // #4020: the commit the run builds from, on its record and on the gate facts it writes.
1451
+ const commit = options.commit ?? (await resolveCommitSha(options.cwd ? { cwd: options.cwd } : {}));
1452
+ // #4020: the CI run this one is in, in the shape a deploy record carries it.
1453
+ const ciRun = resolveCiRun();
1454
+ // #4041: the run's events name it the way its run record does: its own id,
1455
+ // the commit, and the CI run it is in.
1456
+ // #4042: a run that writes to the ledger also keeps its events on this host,
1457
+ // beside its in-flight record, so `chant run events <run>` can replay it and
1458
+ // `--follow` can tail it. The stream is opened for that even without
1459
+ // `--events-json`; a run that records nothing (`--no-record`) keeps nothing.
1460
+ const store = options.ledger
1461
+ ? await openRunEventStore(options.ledger.cwd ?? options.cwd ?? process.cwd(), runId)
1462
+ : undefined;
1463
+ const eventsWrite = teeLines(options.runEvents?.write, store?.write.bind(store));
1464
+ const stream = eventsWrite
1465
+ ? openRunEventStream(
1466
+ runEventRunOf({
1467
+ kind: "op",
1468
+ name: config.name,
1469
+ runId,
1470
+ ...(commit ? { gitSha: commit } : {}),
1471
+ ...(ciRun ? { ci: ciRun } : {}),
1472
+ steward: options.steward !== undefined,
1473
+ ...(options.runEvents?.runtime ? { runtime: options.runEvents.runtime } : {}),
1474
+ ...(options.runEvents?.runOrigin ? { runOrigin: options.runEvents.runOrigin } : {}),
1475
+ }),
1476
+ eventsWrite,
1477
+ )
1478
+ : undefined;
1479
+ events.stream = stream;
1480
+ stream?.emit({ type: "run-start", waves: null });
1349
1481
 
1350
1482
  const gates: GateContext = {
1351
1483
  op: config.name,
1352
1484
  port: options.gates ?? gitGateLedgerPort(options.cwd ? { cwd: options.cwd } : undefined),
1353
1485
  ...(options.now ? { now: options.now } : {}),
1354
1486
  runId,
1487
+ ...(commit ? { gitSha: commit } : {}),
1355
1488
  ...(options.onRecord ? { onRecord: options.onRecord } : {}),
1489
+ ...(stream ? { emit: stream.emit } : {}),
1356
1490
  passed: run.passedGates,
1357
1491
  points: options.points ?? workspaceGatePointAsker(options.cwd ?? process.cwd()),
1358
1492
  ...(options.env !== undefined ? { env: options.env } : {}),
@@ -1440,10 +1574,16 @@ async function runOpInTurn(
1440
1574
  const live = currentLiveRun();
1441
1575
  const t0 = Date.now();
1442
1576
  live?.phaseStarted(phase.name);
1577
+ stream?.emit({ type: "phase-start", component: null, phase: phase.name });
1443
1578
  try {
1444
1579
  const ran = await runPhase(phase, activities, profiles, resultsById, ctx, phaseSignal, from);
1445
1580
  live?.phaseEnded(phase.name, phaseVerdict(ran), Date.now() - t0);
1581
+ stream?.emit({ type: "phase-settle", component: null, phase: phase.name, status: "ok" });
1446
1582
  return ran;
1583
+ } catch (err) {
1584
+ const status = err instanceof GateStop ? "gated" : err instanceof PointStop ? "waiting" : err instanceof WorkStop && !err.lost ? "ok" : "failed";
1585
+ stream?.emit({ type: "phase-settle", component: null, phase: phase.name, status });
1586
+ throw err;
1447
1587
  } finally {
1448
1588
  phaseDurations[phase.name] = (phaseDurations[phase.name] ?? 0) + (Date.now() - t0);
1449
1589
  }
@@ -1471,9 +1611,23 @@ async function runOpInTurn(
1471
1611
  ...(point ? { point: { ...point, since: ended } } : {}),
1472
1612
  ...(options.steward ? { steward: options.steward } : {}),
1473
1613
  ...(resumed ? { resumed } : {}),
1614
+ ...(commit ? { gitSha: commit } : {}),
1615
+ ...(ciRun ? { runOrigin: ciRun.runOrigin } : {}),
1474
1616
  phaseDurations,
1475
1617
  });
1476
- const record: OpRunRecord = { version: 1, ...input, id: runId };
1618
+ let record: OpRunRecord = { version: 1, ...input, id: runId };
1619
+ if (options.signRecord) {
1620
+ try {
1621
+ const statement = options.signRecord(record);
1622
+ input.statement = statement;
1623
+ record = { ...record, statement };
1624
+ } catch (err) {
1625
+ options.onSignError?.(err);
1626
+ }
1627
+ }
1628
+ // The stream settles as the run does, with the first failing step's error.
1629
+ const firstError = status === "fail" ? settled.find((r) => r.status === "fail")?.error : undefined;
1630
+ stream?.settle(status === "fail" ? "failed" : status, firstError);
1477
1631
  if (!options.ledger) return record;
1478
1632
  try {
1479
1633
  return (await appendRunRecord(input, options.ledger)).record;
@@ -1497,7 +1651,7 @@ async function runOpInTurn(
1497
1651
  gates.resume = {
1498
1652
  config,
1499
1653
  gates: new Map(resumeGates.map((g) => [g.step, g])),
1500
- commit: options.commit ?? (await getHeadCommit(options.cwd ? { cwd: options.cwd } : undefined).catch(() => undefined)),
1654
+ commit,
1501
1655
  ...(options.contracts ? { contracts: options.contracts } : {}),
1502
1656
  note: (line) => resumeNotes.push(line),
1503
1657
  };
@@ -1525,10 +1679,11 @@ async function runOpInTurn(
1525
1679
  const steps = pi < gate.phaseIndex ? phase.steps : phase.parallel ? [] : phase.steps.slice(0, gate.stepIndex);
1526
1680
  for (const [si, step] of steps.entries()) {
1527
1681
  if (gate.planStep && pi === gate.planStep.phaseIndex && si === gate.planStep.stepIndex) {
1682
+ startStep(gates, phase.name, gate.planStep.step.fn, si);
1528
1683
  const ran = await runStep(gate.planStep.step, phase.name, activities, profiles, resultsById, stepSignal);
1529
- prefix.push(ran.record);
1684
+ prefix.push(hinted(ran.record, { index: si }));
1530
1685
  if (ran.record.status === "fail" || ran.pending || ran.record.point) {
1531
- for (const r of prefix) gates.onRecord?.(r);
1686
+ for (const r of prefix) announce(gates, r);
1532
1687
  if (ran.pending) throw new GateStop(prefix, ran.pending, phase.name);
1533
1688
  if (ran.record.point) throw new PointStop(prefix, ran.record.point, phase.name);
1534
1689
  throw new PhaseFailure(prefix);
@@ -1540,7 +1695,7 @@ async function runOpInTurn(
1540
1695
  : isGate(step)
1541
1696
  ? skippedRecord(phase.name, gateFn(step))
1542
1697
  : skippedRecord(phase.name, step.fn, step.args);
1543
- prefix.push({ ...skipped, resumed: marker });
1698
+ prefix.push(hinted({ ...skipped, resumed: marker }, { index: si }));
1544
1699
  }
1545
1700
  }
1546
1701
  // The plan the run would approve now against the one approved: a moved
@@ -1556,7 +1711,7 @@ async function runOpInTurn(
1556
1711
  return undefined;
1557
1712
  }
1558
1713
  records.push(...prefix);
1559
- for (const r of prefix) gates.onRecord?.(r);
1714
+ for (const r of prefix) announce(gates, r);
1560
1715
  resumed = { gate: gate.name, run: checkpoint.runId, checkpoint: decision.digest };
1561
1716
  return { phaseIndex: gate.phaseIndex, stepIndex: config.phases[gate.phaseIndex]!.parallel ? 0 : gate.stepIndex };
1562
1717
  };
@@ -71,6 +71,7 @@ import { isApprovalSpent, type AppliedGateRecord } from "../lifecycle/gate-appli
71
71
  import type { HeadPlans, MergedReview, ReviewSource } from "./gate-review";
72
72
  import type { TrustPolicy } from "../workspace/trust/policy";
73
73
  import type { GateSigners } from "../workspace/trust/gate-seal";
74
+ import { ciRunIdFromEnv } from "./gate-run-locator";
74
75
  import {
75
76
  OP_WAVE_DEFAULT_APPLY,
76
77
  OP_WAVE_GATE_POLICIES,
@@ -2228,6 +2229,9 @@ async function approveByReview(
2228
2229
  timestamp: at.now,
2229
2230
  planDigest: at.digest,
2230
2231
  approver: { kind: "human" },
2232
+ // #4020: the commit the wave applies, and the CI run applying it.
2233
+ gitSha: head,
2234
+ ...(ciRunIdFromEnv() ? { runId: ciRunIdFromEnv()! } : {}),
2231
2235
  note,
2232
2236
  ...(merged.url ? { url: merged.url } : {}),
2233
2237
  review: { pr: merged.pr, head: merged.head, approvers: merged.approvers },