@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,206 @@
1
+ /**
2
+ * `chant release` (#4026, ws-104): the release is what graduates.
3
+ *
4
+ * release cut [<label>] [--build <command>] [--param k=v]... [--params-file <file>] [--src <dir>] [-o <file>] [--lexicon <name>] [--actor <name>] [--sign [<key file>]] [--json]
5
+ * cut a release of this member from the checkout's HEAD, which must be green;
6
+ * --sign signs the cut with the actor's ssh key (#4031); `--src`, `-o` and
7
+ * `--lexicon` shape the build of a project with no components (#4046)
8
+ * release list [--json]
9
+ * every release of this member with its targets, its cuts and where it runs;
10
+ * `--json` prints the read contract's `releases` document
11
+ * release export <label|digest> --env <env> [-o <dir>] [--json]
12
+ * write the files the release recorded for <env>, checked against their digests (#4046)
13
+ * release compile --env <env> [--build <command>] [--artifacts --component <a,b>] --json
14
+ * internal: what the cut runs in a child process per environment
15
+ *
16
+ * The workstation UX for `cut` grows in #4027. A declaration whose
17
+ * `identity.gates` names a `release` gate requires a signed cut by an actor
18
+ * in its class (#4031).
19
+ */
20
+
21
+ import { resolve } from "node:path";
22
+ import { formatBold, formatError, formatSuccess, formatWarning } from "../format";
23
+ import { parseParamFlags } from "../build-params-cli";
24
+ import type { CommandContext } from "../registry";
25
+
26
+ const USAGE =
27
+ "chant release cut [<label>] [--build <command>] [--param <k=v>] [--params-file <file>] [--src <dir>] [-o <file>] [--lexicon <name>] [--actor <name>] [--sign [<key file>]] [--json] | release list [--json] | release export <label|digest> --env <env> [-o <dir>] [--json]";
28
+
29
+ /** The build inputs `release cut` and `release compile` share. */
30
+ function compileInputs(ctx: CommandContext, params: Record<string, string> | undefined): import("../../components/release-compile").CompileInputs {
31
+ const { args } = ctx;
32
+ return {
33
+ projectDir: resolve("."),
34
+ ...(args.buildCommand ? { buildCommand: args.buildCommand } : {}),
35
+ ...(params ? { params } : {}),
36
+ ...(args.paramsFile ? { paramsFile: args.paramsFile } : {}),
37
+ ...(args.src ? { src: args.src } : {}),
38
+ ...(args.output ? { output: args.output } : {}),
39
+ ...(args.lexicon ? { lexicon: args.lexicon } : {}),
40
+ };
41
+ }
42
+
43
+ function resolveActor(ctx: CommandContext): string | undefined {
44
+ return ctx.args.actor ?? process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_LOGIN ?? process.env.USER;
45
+ }
46
+
47
+ export async function runReleaseCut(ctx: CommandContext): Promise<number> {
48
+ const { args } = ctx;
49
+ const positional = args.extraPositional;
50
+ if (positional && args.label && positional !== args.label) {
51
+ console.error(formatError({ message: `the label is given twice, as "${positional}" and --label "${args.label}"`, hint: USAGE }));
52
+ return 1;
53
+ }
54
+ const label = positional ?? args.label ?? null;
55
+ const actor = resolveActor(ctx);
56
+ if (!actor) {
57
+ console.error(formatError({ message: "Could not resolve --actor from the environment", hint: "Pass --actor explicitly, or set GITHUB_ACTOR / GITLAB_USER_LOGIN / USER." }));
58
+ return 1;
59
+ }
60
+ const cwd = resolve(".");
61
+ const { spawnTargetCompiler, ReleaseCompileError } = await import("../../components/release-compile");
62
+ const { cutRelease, ReleaseCutError } = await import("../../lifecycle/release-cut");
63
+ const { ReleaseLabelError } = await import("../../lifecycle/release-store");
64
+ const { pushLifecycleReport } = await import("../../lifecycle/git");
65
+ const params = parseParamFlags(args.param);
66
+ const compiler = spawnTargetCompiler(compileInputs(ctx, params));
67
+ try {
68
+ const result = await cutRelease({ cwd, compiler, label, actor, ...(args.sign !== undefined ? { sign: args.sign } : {}) });
69
+ const push = await pushLifecycleReport({ cwd }).catch(() => ({ pushed: false, pushWarning: undefined }));
70
+ if (args.json) {
71
+ console.log(JSON.stringify({ release: result.release, label: result.label, cut: result.cut, stored: result.stored, reused: result.reused }, null, 2));
72
+ } else {
73
+ const name = result.label ? formatBold(result.label) : "unlabelled release";
74
+ if (result.cut) console.error(formatSuccess(`Cut ${name}: ${result.release.releaseDigest}`));
75
+ else console.error(formatSuccess(`Already cut${result.label ? ` as ${formatBold(result.label)}` : ""}: ${result.release.releaseDigest}`));
76
+ console.error(` commit ${result.release.commit}`);
77
+ if (result.cut) console.error(result.cut.signature ? ` signed by ${result.cut.actor}` : " not signed");
78
+ console.error(` artifacts ${result.release.artifactDigests.length}, targets ${result.release.targets.map((t) => `${t.env} (${t.templates.length} template${t.templates.length === 1 ? "" : "s"})`).join(", ")}`);
79
+ for (const r of result.reused) console.error(` ${r.component}: artifacts unchanged, carried from ${r.from}`);
80
+ console.log(result.release.releaseDigest);
81
+ }
82
+ if (push.pushWarning) console.error(formatWarning({ message: push.pushWarning }));
83
+ return 0;
84
+ } catch (err) {
85
+ if (err instanceof ReleaseCutError) {
86
+ console.error(formatError({ message: `${err.message} [${err.code}]`, ...(err.fix ? { hint: err.fix } : {}) }));
87
+ return 1;
88
+ }
89
+ if (err instanceof ReleaseLabelError) {
90
+ console.error(formatError({ message: `${err.message} [${err.code}]` }));
91
+ return 1;
92
+ }
93
+ if (err instanceof ReleaseCompileError) {
94
+ console.error(formatError({ message: `no release was cut: ${err.message}` }));
95
+ return 1;
96
+ }
97
+ throw err;
98
+ }
99
+ }
100
+
101
+ export async function runReleaseList(ctx: CommandContext): Promise<number> {
102
+ const { releasesDocument } = await import("../../workspace/releases");
103
+ const doc = await releasesDocument({ cwd: resolve(".") });
104
+ if (ctx.args.json) {
105
+ console.log(JSON.stringify(doc, null, 2));
106
+ return "error" in doc ? 1 : 0;
107
+ }
108
+ if ("error" in doc) {
109
+ console.error(formatError({ message: doc.error.message, hint: USAGE }));
110
+ return 1;
111
+ }
112
+ if (doc.reason) console.error(formatWarning({ message: doc.reason.message }));
113
+ if (doc.releases.length === 0) {
114
+ console.error("No releases. Cut one from a green commit: chant release cut <label>");
115
+ return 0;
116
+ }
117
+ for (const r of doc.releases) {
118
+ const where = r.environments.map((e) => e.env).join(", ") || "not deployed from the release";
119
+ // #4031: the newest cut's signature, as the policy at base judges it.
120
+ const newest = r.cuts.at(-1);
121
+ const signature = newest ? (newest.signatureCheck.status === "verified" ? `signed by ${newest.actor}` : newest.signatureCheck.status) : "-";
122
+ console.log(`${(r.label ?? "-").padEnd(16)} ${r.releaseDigest} ${r.commit.slice(0, 12)} ${signature} ${where}`);
123
+ if (newest?.signatureCheck.status === "unverified") console.error(` ${r.label ?? r.releaseDigest}: ${newest.signatureCheck.reason}`);
124
+ }
125
+ return 0;
126
+ }
127
+
128
+ /**
129
+ * `release export <label|digest> --env <env> [-o <dir>]` (#4046): write each
130
+ * file the release's target for <env> records, at the path it was compiled to,
131
+ * under <dir> (the member's directory by default), after checking each one's
132
+ * bytes against its digest. This is how a project with no components deploys
133
+ * a release: its own deploy reads these files instead of building again.
134
+ */
135
+ export async function runReleaseExport(ctx: CommandContext): Promise<number> {
136
+ const { args } = ctx;
137
+ const ref = args.extraPositional;
138
+ if (!ref || !args.env) {
139
+ console.error(formatError({ message: "release export needs a release and an environment", hint: USAGE }));
140
+ return 1;
141
+ }
142
+ const { fetchLifecycle } = await import("../../lifecycle/git");
143
+ const { resolveRelease, readReleaseTemplate } = await import("../../lifecycle/release-store");
144
+ const { targetFor, targetFiles, writePinnedTemplates } = await import("../../components/release-promote");
145
+ await fetchLifecycle().catch(() => false);
146
+ const found = await resolveRelease(ref);
147
+ if (!found) {
148
+ console.error(formatError({ message: `no release is labelled or digested "${ref}" in this member`, hint: "List them with: chant release list" }));
149
+ return 1;
150
+ }
151
+ const target = targetFor(found.release, args.env);
152
+ if ("error" in target) {
153
+ console.error(formatError({ message: target.error }));
154
+ return 1;
155
+ }
156
+ const read = await targetFiles(found.release, target.target, (digest) => readReleaseTemplate(digest));
157
+ if ("errors" in read) {
158
+ for (const e of read.errors) console.error(formatError({ message: e }));
159
+ return 1;
160
+ }
161
+ const dir = resolve(args.output ?? ".");
162
+ writePinnedTemplates(dir, read.files);
163
+ if (args.json) {
164
+ console.log(
165
+ JSON.stringify(
166
+ { releaseDigest: found.release.releaseDigest, label: found.label, env: args.env, dir, templates: target.target.templates, params: target.target.params },
167
+ null,
168
+ 2,
169
+ ),
170
+ );
171
+ } else {
172
+ console.error(formatSuccess(`Wrote ${read.files.length} file(s) of ${found.label ? formatBold(found.label) : found.release.releaseDigest} for "${args.env}"`));
173
+ for (const f of read.files) console.log(f.path);
174
+ }
175
+ return 0;
176
+ }
177
+
178
+ /** Internal: compile one environment, or build artifacts, in this process, and print the JSON the cut reads. */
179
+ export async function runReleaseCompile(ctx: CommandContext): Promise<number> {
180
+ const { args } = ctx;
181
+ const env = args.env;
182
+ if (!env) {
183
+ console.error(formatError({ message: "release compile needs --env <env>" }));
184
+ return 1;
185
+ }
186
+ const { compileEnvironment, buildReleaseArtifacts } = await import("../../components/release-compile");
187
+ const params = parseParamFlags(args.param);
188
+ const inputs = compileInputs(ctx, params);
189
+ try {
190
+ if (args.artifacts) {
191
+ const names = (args.component ?? "").split(",").map((n) => n.trim()).filter(Boolean);
192
+ console.log(JSON.stringify({ artifacts: await buildReleaseArtifacts(inputs, env, names) }));
193
+ } else {
194
+ console.log(JSON.stringify(await compileEnvironment(inputs, env)));
195
+ }
196
+ return 0;
197
+ } catch (err) {
198
+ console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
199
+ return 1;
200
+ }
201
+ }
202
+
203
+ export async function runReleaseUnknown(ctx: CommandContext): Promise<number> {
204
+ console.error(formatError({ message: `Unknown release subcommand: ${ctx.args.path}`, hint: USAGE }));
205
+ return 1;
206
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * What `recordAutoReleasesForRun` hands the release record (#4020): the run
3
+ * and its origin, and for a component that passed a gate, who approved it and
4
+ * which resolution admitted it.
5
+ */
6
+ import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
7
+
8
+ const maybeRecordAutoReleaseMock = vi.fn();
9
+ vi.mock("../../components/auto-release", async () => {
10
+ const actual = await vi.importActual<typeof import("../../components/auto-release")>("../../components/auto-release");
11
+ return { ...actual, maybeRecordAutoRelease: (...args: unknown[]) => maybeRecordAutoReleaseMock(...args) };
12
+ });
13
+ vi.mock("../../components/manifest-persistence", async () => {
14
+ const actual = await vi.importActual<typeof import("../../components/manifest-persistence")>("../../components/manifest-persistence");
15
+ return { ...actual, maybePersistBuildManifest: async () => ({ persisted: false, reason: "no-manifest" }) };
16
+ });
17
+
18
+ import { autoReleaseRun, recordAutoReleasesForRun } from "./run";
19
+ import type { DriverComponentResult } from "../../components/driver";
20
+
21
+ const gatedResult = {
22
+ component: "api",
23
+ ok: true,
24
+ status: "ok",
25
+ records: [
26
+ { component: "api", phase: "Deploy", kind: "gate:approve-prod", status: "ok", durationMs: 1, approval: { gate: "approve-prod", resolvedBy: "alice", timestamp: "2026-10-16T10:05:00.000Z" } },
27
+ { component: "api", phase: "Deploy", kind: "apply", status: "ok", durationMs: 1 },
28
+ ],
29
+ } as unknown as DriverComponentResult;
30
+ const plainResult = { component: "worker", ok: true, status: "ok", records: [] } as unknown as DriverComponentResult;
31
+
32
+ beforeEach(() => {
33
+ maybeRecordAutoReleaseMock.mockReset().mockResolvedValue({ recorded: false, reason: "no-digest" });
34
+ });
35
+ afterEach(() => vi.unstubAllEnvs());
36
+
37
+ describe("recordAutoReleasesForRun", () => {
38
+ test("a component that passed a gate records its approver and admission, one that did not records neither", async () => {
39
+ await recordAutoReleasesForRun([gatedResult, plainResult], "prod", "9001", false, { forge: "github", repo: "acme/app" });
40
+ const [gated, plain] = maybeRecordAutoReleaseMock.mock.calls.map((c) => c[0]);
41
+ expect(gated).toMatchObject({
42
+ component: "api", runId: "9001", runOrigin: { forge: "github", repo: "acme/app" },
43
+ approver: "alice", admission: { op: "api", gate: "approve-prod", resolvedAt: "2026-10-16T10:05:00.000Z" },
44
+ });
45
+ expect(plain).not.toHaveProperty("approver");
46
+ expect(plain).not.toHaveProperty("admission");
47
+ });
48
+
49
+ test("the origin defaults to local", async () => {
50
+ await recordAutoReleasesForRun([plainResult], "prod", "local-1", false);
51
+ expect(maybeRecordAutoReleaseMock.mock.calls[0][0]).toMatchObject({ runId: "local-1", runOrigin: { forge: "local" } });
52
+ });
53
+ });
54
+
55
+ describe("autoReleaseRun", () => {
56
+ test("in CI it names the CI run, so the release and the gate it passed name one run", () => {
57
+ vi.stubEnv("GITHUB_RUN_ID", "9001");
58
+ vi.stubEnv("GITHUB_REPOSITORY", "acme/app");
59
+ vi.stubEnv("GITHUB_SERVER_URL", "https://github.example");
60
+ expect(autoReleaseRun("local-1")).toEqual({
61
+ runId: "9001",
62
+ runOrigin: { forge: "github", repo: "acme/app", url: "https://github.example/acme/app/actions/runs/9001" },
63
+ });
64
+ });
65
+
66
+ test("outside CI it keeps the local id", () => {
67
+ vi.stubEnv("GITHUB_RUN_ID", "");
68
+ vi.stubEnv("CI_PIPELINE_ID", "");
69
+ expect(autoReleaseRun("local-1")).toEqual({ runId: "local-1", runOrigin: { forge: "local" } });
70
+ });
71
+ });
@@ -0,0 +1,92 @@
1
+ /** #4042: `chant run events <run> [--json] [--follow]`. */
2
+
3
+ import { execFileSync } from "node:child_process";
4
+ import { mkdtempSync, realpathSync, rmSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
8
+ import type { ParsedArgs } from "../registry";
9
+ import { runOpEvents } from "./run";
10
+ import { openRunEventStore } from "../../run-event-store";
11
+ import { openRunEventStream, runEventRunOf } from "../../run-event-stream";
12
+
13
+ function args(o: Partial<ParsedArgs>): ParsedArgs {
14
+ return { command: "run", path: ".", format: "", fix: false, watch: false, verbose: false, help: false, live: false, ...o };
15
+ }
16
+
17
+ let dir: string;
18
+ let cwd: string;
19
+ let out: string[];
20
+ beforeEach(() => {
21
+ cwd = process.cwd();
22
+ vi.stubEnv("CHANT_RUN_EVENTS_DIR", "");
23
+ dir = realpathSync(mkdtempSync(join(tmpdir(), "run-events-cmd-")));
24
+ execFileSync("git", ["init", "-q", "-b", "main"], { cwd: dir });
25
+ execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "commit", "-q", "--allow-empty", "-m", "init"], { cwd: dir });
26
+ process.chdir(dir);
27
+ out = [];
28
+ vi.spyOn(process.stdout, "write").mockImplementation((s: string | Uint8Array) => (out.push(String(s)), true));
29
+ vi.spyOn(console, "log").mockImplementation((s: string) => void out.push(`${s}\n`));
30
+ vi.spyOn(console, "error").mockImplementation(() => {});
31
+ });
32
+ afterEach(() => {
33
+ vi.restoreAllMocks();
34
+ vi.unstubAllEnvs();
35
+ process.chdir(cwd);
36
+ rmSync(dir, { recursive: true, force: true });
37
+ });
38
+
39
+ async function stream(runId: string, name = "deploy-api") {
40
+ const store = (await openRunEventStore(dir, runId))!;
41
+ return openRunEventStream(runEventRunOf({ kind: "op", name, runId }), store.write);
42
+ }
43
+
44
+ describe("chant run events", () => {
45
+ test("--json replays a settled run's lines and exits with its verdict", async () => {
46
+ const s = await stream("r-done");
47
+ s.emit({ type: "run-start", waves: null });
48
+ s.settle("failed", "boom");
49
+ const exit = await runOpEvents({ args: args({ extraPositional: "r-done", json: true }), plugins: [], serializers: [] });
50
+ expect(exit).toBe(1);
51
+ const events = out.join("").trim().split("\n").map((l) => JSON.parse(l));
52
+ expect(events.map((e) => e.type)).toEqual(["run-start", "run-settle"]);
53
+ expect(events[1]).toMatchObject({ status: "failed", error: "boom" });
54
+ });
55
+
56
+ test("--json replays a run in progress up to now and exits 0", async () => {
57
+ const s = await stream("r-live");
58
+ s.emit({ type: "run-start", waves: null });
59
+ s.emit({ type: "phase-start", component: null, phase: "Build" });
60
+ const exit = await runOpEvents({ args: args({ extraPositional: "deploy-api", json: true }), plugins: [], serializers: [] });
61
+ expect(exit).toBe(0);
62
+ expect(out.join("").trim().split("\n").map((l) => JSON.parse(l).type)).toEqual(["run-start", "phase-start"]);
63
+ });
64
+
65
+ test("--follow prints each event of a run in progress, then the verdict", async () => {
66
+ const s = await stream("r-follow");
67
+ s.emit({ type: "run-start", waves: null });
68
+ const done = runOpEvents({ args: args({ extraPositional: "r-follow", json: true, follow: true }), plugins: [], serializers: [] });
69
+ await new Promise((r) => setTimeout(r, 150));
70
+ expect(out.join("").trim().split("\n")).toHaveLength(1);
71
+ s.emit({ type: "phase-start", component: null, phase: "Build" });
72
+ s.emit({ type: "phase-settle", component: null, phase: "Build", status: "gated" });
73
+ s.settle("gated");
74
+ expect(await done).toBe(3);
75
+ expect(out.join("").trim().split("\n").map((l) => JSON.parse(l).type)).toEqual(["run-start", "phase-start", "phase-settle", "run-settle"]);
76
+ });
77
+
78
+ test("without --json each event is one line for a person", async () => {
79
+ const s = await stream("r-human");
80
+ s.emit({ type: "run-start", waves: null });
81
+ s.settle("ok");
82
+ expect(await runOpEvents({ args: args({ extraPositional: "r-human" }), plugins: [], serializers: [] })).toBe(0);
83
+ expect(out.join("")).toMatch(/run-start/);
84
+ expect(out.join("")).toMatch(/run-settle\s+ok/);
85
+ });
86
+
87
+ test("a run with no kept events is refused by name; no run is refused", async () => {
88
+ expect(await runOpEvents({ args: args({ extraPositional: "nope" }), plugins: [], serializers: [] })).toBe(1);
89
+ expect(await runOpEvents({ args: args({}), plugins: [], serializers: [] })).toBe(1);
90
+ expect(await runOpEvents({ args: args({ extraPositional: "x", on: "fountain" }), plugins: [], serializers: [] })).toBe(1);
91
+ });
92
+ });
@@ -98,6 +98,7 @@ vi.mock("../../lifecycle/run-ledger", async (importOriginal) => {
98
98
 
99
99
  const { runOpList, runOpStatus, runOpLog, runOpSignalRenamed, runOpApprove, runOpCancel, runOp, runOpComponents } =
100
100
  await import("./run");
101
+ const { checkRunEventLines } = await import("../../workspace/__fixtures__/run-event-check");
101
102
 
102
103
  function makeArgs(overrides: Partial<ParsedArgs> = {}): ParsedArgs {
103
104
  return {
@@ -1554,6 +1555,35 @@ describe("runOpComponents", () => {
1554
1555
  vi.restoreAllMocks();
1555
1556
  });
1556
1557
 
1558
+ test("--events-json streams the run-event stream as runComponents emits it, and ends one that never reached a component (#4041)", async () => {
1559
+ runComponentsMock.mockImplementation(
1560
+ async (_path: string, _selector: string, options: { onRunEvent?: (b: unknown) => void }) => {
1561
+ options.onRunEvent?.({ type: "run-start", waves: [["svc"]] });
1562
+ options.onRunEvent?.({ type: "run-settle", status: "ok", durationMs: null });
1563
+ return {
1564
+ success: true,
1565
+ selected: ["svc"],
1566
+ run: { order: ["svc"], waves: [["svc"]], results: [{ component: "svc", ok: true, status: "ok", records: [] }], ok: true, status: "ok" },
1567
+ };
1568
+ },
1569
+ );
1570
+ const stdoutWrite = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
1571
+ vi.spyOn(process.stderr, "write").mockImplementation(() => true);
1572
+
1573
+ expect(await runOpComponents({ args: makeArgs({ path: "svc", eventsJson: true }), plugins: [], serializers: [] })).toBe(0);
1574
+ const events = checkRunEventLines(stdoutWrite.mock.calls.map((c) => String(c[0])));
1575
+ expect(events[0]).toMatchObject({ type: "run-start", kind: "component", name: "svc", waves: [["svc"]] });
1576
+ expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "ok" });
1577
+
1578
+ stdoutWrite.mockClear();
1579
+ runComponentsMock.mockReset().mockResolvedValue({ success: false, selected: [], error: "no component named svc" });
1580
+ expect(await runOpComponents({ args: makeArgs({ path: "svc", eventsJson: true }), plugins: [], serializers: [] })).toBe(1);
1581
+ const failed = checkRunEventLines(stdoutWrite.mock.calls.map((c) => String(c[0])));
1582
+ expect(failed.map((e) => e.type)).toEqual(["run-start", "run-settle"]);
1583
+ expect(failed.at(-1)).toMatchObject({ status: "failed", error: "no component named svc" });
1584
+ vi.restoreAllMocks();
1585
+ });
1586
+
1557
1587
  test("without --progress-json, runComponents receives onProgress: undefined and nothing streams", async () => {
1558
1588
  runComponentsMock.mockResolvedValue({
1559
1589
  success: true,