@intentius/chant 0.124.27 → 0.124.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (681) hide show
  1. package/README.md +1 -1
  2. package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
  3. package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
  4. package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  6. package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
  7. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  8. package/dist/__golden__/wave-loop/op-held-on.json +2 -0
  9. package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
  10. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  11. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  12. package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
  13. package/dist/__golden__/wave-loop/op-per-run.json +7 -0
  14. package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
  15. package/dist/__golden__/wave-loop/op-sealed.json +3 -0
  16. package/dist/__golden__/wave-loop/op-spend.json +3 -0
  17. package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
  18. package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
  19. package/dist/build-params.d.ts +34 -1
  20. package/dist/build-params.d.ts.map +1 -1
  21. package/dist/build-params.js +31 -1
  22. package/dist/change-set-graph.d.ts +63 -0
  23. package/dist/change-set-graph.d.ts.map +1 -0
  24. package/dist/change-set-graph.js +130 -0
  25. package/dist/change-set.d.ts +62 -3
  26. package/dist/change-set.d.ts.map +1 -1
  27. package/dist/change-set.js +36 -4
  28. package/dist/chant-version.d.ts +9 -0
  29. package/dist/chant-version.d.ts.map +1 -0
  30. package/dist/chant-version.js +16 -0
  31. package/dist/cli/build-params-cli.d.ts +7 -1
  32. package/dist/cli/build-params-cli.d.ts.map +1 -1
  33. package/dist/cli/build-params-cli.js +17 -8
  34. package/dist/cli/commands/build.d.ts +8 -0
  35. package/dist/cli/commands/build.d.ts.map +1 -1
  36. package/dist/cli/commands/build.js +15 -11
  37. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  38. package/dist/cli/commands/check-lexicon-docs.js +30 -2
  39. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  40. package/dist/cli/commands/check-lexicon.js +13 -2
  41. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
  42. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  43. package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
  44. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  45. package/dist/cli/commands/init-lexicon.js +17 -8
  46. package/dist/cli/handlers/approve.d.ts.map +1 -1
  47. package/dist/cli/handlers/approve.js +64 -4
  48. package/dist/cli/handlers/build.d.ts.map +1 -1
  49. package/dist/cli/handlers/build.js +43 -4
  50. package/dist/cli/handlers/change-set.d.ts +10 -0
  51. package/dist/cli/handlers/change-set.d.ts.map +1 -1
  52. package/dist/cli/handlers/change-set.js +51 -1
  53. package/dist/cli/handlers/fan-out.d.ts +10 -1
  54. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  55. package/dist/cli/handlers/fan-out.js +54 -6
  56. package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
  57. package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
  58. package/dist/cli/handlers/lifecycle-gc.js +143 -0
  59. package/dist/cli/handlers/pr.d.ts +16 -2
  60. package/dist/cli/handlers/pr.d.ts.map +1 -1
  61. package/dist/cli/handlers/pr.js +269 -37
  62. package/dist/cli/handlers/promote.d.ts +7 -0
  63. package/dist/cli/handlers/promote.d.ts.map +1 -1
  64. package/dist/cli/handlers/promote.js +440 -28
  65. package/dist/cli/handlers/release.d.ts +91 -0
  66. package/dist/cli/handlers/release.d.ts.map +1 -0
  67. package/dist/cli/handlers/release.js +589 -0
  68. package/dist/cli/handlers/run.d.ts +38 -2
  69. package/dist/cli/handlers/run.d.ts.map +1 -1
  70. package/dist/cli/handlers/run.js +234 -16
  71. package/dist/cli/main.d.ts.map +1 -1
  72. package/dist/cli/main.js +143 -17
  73. package/dist/cli/mcp/workspace-tools.js +1 -1
  74. package/dist/cli/parse-args.d.ts.map +1 -1
  75. package/dist/cli/parse-args.js +73 -5
  76. package/dist/cli/registry.d.ts +43 -5
  77. package/dist/cli/registry.d.ts.map +1 -1
  78. package/dist/cli/run-events.d.ts +30 -0
  79. package/dist/cli/run-events.d.ts.map +1 -0
  80. package/dist/cli/run-events.js +49 -0
  81. package/dist/cli/version.d.ts +3 -5
  82. package/dist/cli/version.d.ts.map +1 -1
  83. package/dist/cli/version.js +3 -13
  84. package/dist/codegen/docs-in-site.d.ts +71 -0
  85. package/dist/codegen/docs-in-site.d.ts.map +1 -0
  86. package/dist/codegen/docs-in-site.js +177 -0
  87. package/dist/codegen/docs-types.d.ts +14 -1
  88. package/dist/codegen/docs-types.d.ts.map +1 -1
  89. package/dist/codegen/docs.d.ts +18 -1
  90. package/dist/codegen/docs.d.ts.map +1 -1
  91. package/dist/codegen/docs.js +40 -7
  92. package/dist/codegen/rehype-base-url.mjs +15 -9
  93. package/dist/components/auto-release.d.ts +6 -1
  94. package/dist/components/auto-release.d.ts.map +1 -1
  95. package/dist/components/auto-release.js +3 -0
  96. package/dist/components/capability.d.ts +9 -0
  97. package/dist/components/capability.d.ts.map +1 -1
  98. package/dist/components/cli-support.d.ts +6 -0
  99. package/dist/components/cli-support.d.ts.map +1 -1
  100. package/dist/components/cli-support.js +10 -9
  101. package/dist/components/driver.d.ts +50 -9
  102. package/dist/components/driver.d.ts.map +1 -1
  103. package/dist/components/driver.js +116 -54
  104. package/dist/components/fan-out-inputs.d.ts +69 -0
  105. package/dist/components/fan-out-inputs.d.ts.map +1 -0
  106. package/dist/components/fan-out-inputs.js +169 -0
  107. package/dist/components/fan-out-record.d.ts +13 -0
  108. package/dist/components/fan-out-record.d.ts.map +1 -1
  109. package/dist/components/fan-out-record.js +16 -0
  110. package/dist/components/fan-out-run.d.ts +7 -0
  111. package/dist/components/fan-out-run.d.ts.map +1 -1
  112. package/dist/components/fan-out-run.js +85 -26
  113. package/dist/components/fan-out.d.ts +4 -0
  114. package/dist/components/fan-out.d.ts.map +1 -1
  115. package/dist/components/pr-pipeline.d.ts +25 -0
  116. package/dist/components/pr-pipeline.d.ts.map +1 -1
  117. package/dist/components/pr-pipeline.js +22 -4
  118. package/dist/components/pr-run.d.ts +11 -0
  119. package/dist/components/pr-run.d.ts.map +1 -1
  120. package/dist/components/pr-run.js +20 -1
  121. package/dist/components/promote-order.d.ts +38 -0
  122. package/dist/components/promote-order.d.ts.map +1 -0
  123. package/dist/components/promote-order.js +62 -0
  124. package/dist/components/promote.d.ts +38 -2
  125. package/dist/components/promote.d.ts.map +1 -1
  126. package/dist/components/promote.js +77 -2
  127. package/dist/components/release-compile.d.ts +142 -0
  128. package/dist/components/release-compile.d.ts.map +1 -0
  129. package/dist/components/release-compile.js +382 -0
  130. package/dist/components/release-gate.d.ts +135 -0
  131. package/dist/components/release-gate.d.ts.map +1 -0
  132. package/dist/components/release-gate.js +288 -0
  133. package/dist/components/release-promote.d.ts +96 -0
  134. package/dist/components/release-promote.d.ts.map +1 -0
  135. package/dist/components/release-promote.js +204 -0
  136. package/dist/components/run-progress.d.ts +7 -0
  137. package/dist/components/run-progress.d.ts.map +1 -1
  138. package/dist/components/wave-plan.d.ts +8 -1
  139. package/dist/components/wave-plan.d.ts.map +1 -1
  140. package/dist/components/wave-plan.js +11 -3
  141. package/dist/config.d.ts +45 -0
  142. package/dist/config.d.ts.map +1 -1
  143. package/dist/config.js +3 -0
  144. package/dist/flag-params.d.ts +54 -0
  145. package/dist/flag-params.d.ts.map +1 -0
  146. package/dist/flag-params.js +130 -0
  147. package/dist/index.d.ts +1 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +2 -0
  150. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  151. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  152. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  153. package/dist/lifecycle/change-set-store.d.ts +68 -0
  154. package/dist/lifecycle/change-set-store.d.ts.map +1 -0
  155. package/dist/lifecycle/change-set-store.js +97 -0
  156. package/dist/lifecycle/commit-sha.d.ts +13 -0
  157. package/dist/lifecycle/commit-sha.d.ts.map +1 -0
  158. package/dist/lifecycle/commit-sha.js +21 -0
  159. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  160. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  161. package/dist/lifecycle/delivery-metrics.js +145 -0
  162. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  163. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  164. package/dist/lifecycle/deploy-outcome.js +101 -0
  165. package/dist/lifecycle/gate-change-set.d.ts +78 -0
  166. package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
  167. package/dist/lifecycle/gate-change-set.js +138 -0
  168. package/dist/lifecycle/gate-ledger.d.ts +51 -4
  169. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  170. package/dist/lifecycle/gate-ledger.js +18 -0
  171. package/dist/lifecycle/git.d.ts +30 -1
  172. package/dist/lifecycle/git.d.ts.map +1 -1
  173. package/dist/lifecycle/git.js +31 -5
  174. package/dist/lifecycle/index.d.ts +4 -0
  175. package/dist/lifecycle/index.d.ts.map +1 -1
  176. package/dist/lifecycle/index.js +4 -0
  177. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  178. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  179. package/dist/lifecycle/mixed-environment.js +154 -0
  180. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  181. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  182. package/dist/lifecycle/publish-attachments.js +179 -0
  183. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  184. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  185. package/dist/lifecycle/publish-credentials.js +69 -0
  186. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  187. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  188. package/dist/lifecycle/publish-declaration.js +83 -0
  189. package/dist/lifecycle/publish-forge.d.ts +53 -0
  190. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  191. package/dist/lifecycle/publish-forge.js +298 -0
  192. package/dist/lifecycle/publish-npm.d.ts +61 -0
  193. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  194. package/dist/lifecycle/publish-npm.js +242 -0
  195. package/dist/lifecycle/publish-oci.d.ts +64 -0
  196. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  197. package/dist/lifecycle/publish-oci.js +383 -0
  198. package/dist/lifecycle/publish-record.d.ts +66 -0
  199. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  200. package/dist/lifecycle/publish-record.js +84 -0
  201. package/dist/lifecycle/publish-targets.d.ts +45 -0
  202. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  203. package/dist/lifecycle/publish-targets.js +85 -0
  204. package/dist/lifecycle/release-cut.d.ts +154 -0
  205. package/dist/lifecycle/release-cut.d.ts.map +1 -0
  206. package/dist/lifecycle/release-cut.js +366 -0
  207. package/dist/lifecycle/release-join.d.ts +95 -0
  208. package/dist/lifecycle/release-join.d.ts.map +1 -0
  209. package/dist/lifecycle/release-join.js +139 -0
  210. package/dist/lifecycle/release-ledger.d.ts +54 -0
  211. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  212. package/dist/lifecycle/release-ledger.js +53 -20
  213. package/dist/lifecycle/release-publish.d.ts +312 -0
  214. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  215. package/dist/lifecycle/release-publish.js +335 -0
  216. package/dist/lifecycle/release-revocation.d.ts +80 -0
  217. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  218. package/dist/lifecycle/release-revocation.js +123 -0
  219. package/dist/lifecycle/release-store.d.ts +200 -0
  220. package/dist/lifecycle/release-store.d.ts.map +1 -0
  221. package/dist/lifecycle/release-store.js +320 -0
  222. package/dist/lifecycle/release-verify.d.ts +61 -0
  223. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  224. package/dist/lifecycle/release-verify.js +92 -0
  225. package/dist/lifecycle/release-workstation.d.ts +91 -0
  226. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  227. package/dist/lifecycle/release-workstation.js +180 -0
  228. package/dist/lifecycle/retention-policy.d.ts +45 -0
  229. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  230. package/dist/lifecycle/retention-policy.js +43 -0
  231. package/dist/lifecycle/retention.d.ts +185 -0
  232. package/dist/lifecycle/retention.d.ts.map +1 -0
  233. package/dist/lifecycle/retention.js +400 -0
  234. package/dist/lifecycle/run-ledger.d.ts +4 -0
  235. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  236. package/dist/lifecycle/run-ledger.js +2 -0
  237. package/dist/op/activities/change-coverage.d.ts.map +1 -1
  238. package/dist/op/activities/change-coverage.js +4 -2
  239. package/dist/op/activities/source-release.d.ts.map +1 -1
  240. package/dist/op/activities/source-release.js +7 -3
  241. package/dist/op/activities/source-rollback.d.ts.map +1 -1
  242. package/dist/op/activities/source-rollback.js +7 -3
  243. package/dist/op/gate-name.d.ts +11 -2
  244. package/dist/op/gate-name.d.ts.map +1 -1
  245. package/dist/op/gate-name.js +12 -3
  246. package/dist/op/gate-run-locator.d.ts +22 -0
  247. package/dist/op/gate-run-locator.d.ts.map +1 -1
  248. package/dist/op/gate-run-locator.js +31 -0
  249. package/dist/op/gate.d.ts +27 -3
  250. package/dist/op/gate.d.ts.map +1 -1
  251. package/dist/op/gate.js +57 -3
  252. package/dist/op/local-executor.d.ts +36 -1
  253. package/dist/op/local-executor.d.ts.map +1 -1
  254. package/dist/op/local-executor.js +158 -52
  255. package/dist/op/op-waves-run.d.ts.map +1 -1
  256. package/dist/op/op-waves-run.js +4 -0
  257. package/dist/op/run-live.d.ts +2 -0
  258. package/dist/op/run-live.d.ts.map +1 -1
  259. package/dist/op/run-live.js +1 -1
  260. package/dist/op/runtime.d.ts +46 -0
  261. package/dist/op/runtime.d.ts.map +1 -1
  262. package/dist/op/runtimes/local.d.ts.map +1 -1
  263. package/dist/op/runtimes/local.js +8 -0
  264. package/dist/plan-summary.d.ts.map +1 -1
  265. package/dist/plan-summary.js +8 -2
  266. package/dist/point-decision.d.ts +2 -0
  267. package/dist/point-decision.d.ts.map +1 -1
  268. package/dist/point-decision.js +9 -2
  269. package/dist/pr-loop.d.ts +7 -0
  270. package/dist/pr-loop.d.ts.map +1 -1
  271. package/dist/pr-loop.js +23 -4
  272. package/dist/pr-report-url.d.ts +82 -0
  273. package/dist/pr-report-url.d.ts.map +1 -0
  274. package/dist/pr-report-url.js +174 -0
  275. package/dist/provenance.d.ts +2 -2
  276. package/dist/provenance.d.ts.map +1 -1
  277. package/dist/run-event-store.d.ts +104 -0
  278. package/dist/run-event-store.d.ts.map +1 -0
  279. package/dist/run-event-store.js +251 -0
  280. package/dist/run-event-stream.d.ts +133 -0
  281. package/dist/run-event-stream.d.ts.map +1 -0
  282. package/dist/run-event-stream.js +242 -0
  283. package/dist/run-event.d.ts +285 -0
  284. package/dist/run-event.d.ts.map +1 -0
  285. package/dist/run-event.js +209 -0
  286. package/dist/wave-runtime.mjs +262 -226
  287. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  288. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  289. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  290. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  291. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  292. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  293. package/dist/workspace/change-set-show.d.ts +56 -0
  294. package/dist/workspace/change-set-show.d.ts.map +1 -0
  295. package/dist/workspace/change-set-show.js +54 -0
  296. package/dist/workspace/change-set-show.schema.json +102 -0
  297. package/dist/workspace/change-set.schema.json +32 -1
  298. package/dist/workspace/changes-cli.d.ts.map +1 -1
  299. package/dist/workspace/changes-cli.js +10 -1
  300. package/dist/workspace/changes.d.ts +37 -0
  301. package/dist/workspace/changes.d.ts.map +1 -1
  302. package/dist/workspace/changes.js +49 -2
  303. package/dist/workspace/changes.schema.json +135 -0
  304. package/dist/workspace/check.schema.json +4 -2
  305. package/dist/workspace/checks/records.d.ts +9 -1
  306. package/dist/workspace/checks/records.d.ts.map +1 -1
  307. package/dist/workspace/checks/records.js +35 -1
  308. package/dist/workspace/checks.d.ts +1 -0
  309. package/dist/workspace/checks.d.ts.map +1 -1
  310. package/dist/workspace/checks.js +2 -1
  311. package/dist/workspace/ci-green-cli.d.ts +19 -4
  312. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  313. package/dist/workspace/ci-green-cli.js +75 -12
  314. package/dist/workspace/ci-green-forge.d.ts +20 -3
  315. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  316. package/dist/workspace/ci-green-forge.js +6 -4
  317. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  318. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  319. package/dist/workspace/ci-green-workflow.js +6 -0
  320. package/dist/workspace/ci-green.d.ts +281 -13
  321. package/dist/workspace/ci-green.d.ts.map +1 -1
  322. package/dist/workspace/ci-green.js +456 -38
  323. package/dist/workspace/ci-last-green.schema.json +32 -2
  324. package/dist/workspace/ci-list.schema.json +525 -0
  325. package/dist/workspace/commit-links.d.ts +85 -0
  326. package/dist/workspace/commit-links.d.ts.map +1 -0
  327. package/dist/workspace/commit-links.js +163 -0
  328. package/dist/workspace/declaration.d.ts +35 -0
  329. package/dist/workspace/declaration.d.ts.map +1 -1
  330. package/dist/workspace/declaration.js +27 -1
  331. package/dist/workspace/declaration.schema.json +76 -0
  332. package/dist/workspace/declared-kinds.d.ts +16 -0
  333. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  334. package/dist/workspace/declared-kinds.js +25 -0
  335. package/dist/workspace/delivery-metrics.d.ts +91 -0
  336. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  337. package/dist/workspace/delivery-metrics.js +217 -0
  338. package/dist/workspace/delivery-metrics.schema.json +519 -0
  339. package/dist/workspace/flags-read.d.ts +37 -0
  340. package/dist/workspace/flags-read.d.ts.map +1 -0
  341. package/dist/workspace/flags-read.js +103 -0
  342. package/dist/workspace/flags.d.ts +100 -0
  343. package/dist/workspace/flags.d.ts.map +1 -0
  344. package/dist/workspace/flags.js +98 -0
  345. package/dist/workspace/graph-cli.js +1 -1
  346. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  347. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  348. package/dist/workspace/identity-gate-rule.js +72 -7
  349. package/dist/workspace/identity.d.ts +12 -0
  350. package/dist/workspace/identity.d.ts.map +1 -1
  351. package/dist/workspace/identity.js +13 -1
  352. package/dist/workspace/intent-cli.d.ts +11 -2
  353. package/dist/workspace/intent-cli.d.ts.map +1 -1
  354. package/dist/workspace/intent-cli.js +54 -5
  355. package/dist/workspace/intent-trace.d.ts +219 -0
  356. package/dist/workspace/intent-trace.d.ts.map +1 -0
  357. package/dist/workspace/intent-trace.js +314 -0
  358. package/dist/workspace/intent.d.ts +36 -7
  359. package/dist/workspace/intent.d.ts.map +1 -1
  360. package/dist/workspace/intent.js +192 -22
  361. package/dist/workspace/intent.schema.json +702 -6
  362. package/dist/workspace/ls.d.ts +4 -0
  363. package/dist/workspace/ls.d.ts.map +1 -1
  364. package/dist/workspace/ls.js +2 -0
  365. package/dist/workspace/ls.schema.json +3 -1
  366. package/dist/workspace/member-pipeline.d.ts +6 -0
  367. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  368. package/dist/workspace/member-pipeline.js +6 -0
  369. package/dist/workspace/overlap.d.ts +146 -0
  370. package/dist/workspace/overlap.d.ts.map +1 -0
  371. package/dist/workspace/overlap.js +276 -0
  372. package/dist/workspace/promotion-order.d.ts +41 -0
  373. package/dist/workspace/promotion-order.d.ts.map +1 -0
  374. package/dist/workspace/promotion-order.js +95 -0
  375. package/dist/workspace/reason-codes.d.ts +4 -0
  376. package/dist/workspace/reason-codes.d.ts.map +1 -1
  377. package/dist/workspace/reason-codes.js +7 -1
  378. package/dist/workspace/records.d.ts +15 -2
  379. package/dist/workspace/records.d.ts.map +1 -1
  380. package/dist/workspace/records.js +32 -0
  381. package/dist/workspace/records.schema.json +21 -2
  382. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  383. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  384. package/dist/workspace/releases.d.ts +81 -0
  385. package/dist/workspace/releases.d.ts.map +1 -0
  386. package/dist/workspace/releases.js +103 -0
  387. package/dist/workspace/releases.schema.json +513 -0
  388. package/dist/workspace/run-event.schema.json +295 -0
  389. package/dist/workspace/status-gates.d.ts +34 -1
  390. package/dist/workspace/status-gates.d.ts.map +1 -1
  391. package/dist/workspace/status-gates.js +31 -2
  392. package/dist/workspace/status-stewards.d.ts +4 -0
  393. package/dist/workspace/status-stewards.d.ts.map +1 -1
  394. package/dist/workspace/status-stewards.js +2 -0
  395. package/dist/workspace/status.d.ts +117 -0
  396. package/dist/workspace/status.d.ts.map +1 -1
  397. package/dist/workspace/status.js +218 -4
  398. package/dist/workspace/status.schema.json +268 -2
  399. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  400. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  401. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  402. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  403. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  404. package/dist/workspace/trust/op-run-statement.js +143 -0
  405. package/dist/workspace/trust/rotation.d.ts +18 -2
  406. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  407. package/dist/workspace/trust/rotation.js +65 -16
  408. package/dist/workspace/trust/seal.d.ts +11 -0
  409. package/dist/workspace/trust/seal.d.ts.map +1 -1
  410. package/dist/workspace/trust/seal.js +9 -0
  411. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  412. package/dist/workspace/trust/ssh-commit.js +10 -10
  413. package/dist/workspace/wip.d.ts.map +1 -1
  414. package/dist/workspace/wip.js +11 -3
  415. package/package.json +1 -1
  416. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  417. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  418. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  419. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  420. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  421. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  422. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  423. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  424. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  425. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  426. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  427. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  428. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  429. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  430. package/src/__golden__/wave-loop/op-spend.json +3 -0
  431. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  432. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  433. package/src/build-params.test.ts +46 -0
  434. package/src/build-params.ts +55 -1
  435. package/src/change-set-graph.test.ts +72 -0
  436. package/src/change-set-graph.ts +141 -0
  437. package/src/change-set.ts +87 -6
  438. package/src/chant-version.ts +16 -0
  439. package/src/cli/build-params-cli.ts +22 -9
  440. package/src/cli/commands/build.ts +25 -11
  441. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  442. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  443. package/src/cli/commands/check-lexicon.ts +14 -2
  444. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  445. package/src/cli/commands/init-lexicon.test.ts +25 -0
  446. package/src/cli/commands/init-lexicon.ts +18 -8
  447. package/src/cli/handlers/approve-release.test.ts +84 -0
  448. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  449. package/src/cli/handlers/approve.ts +65 -2
  450. package/src/cli/handlers/build.test.ts +67 -0
  451. package/src/cli/handlers/build.ts +46 -4
  452. package/src/cli/handlers/change-set.ts +49 -1
  453. package/src/cli/handlers/fan-out.test.ts +228 -1
  454. package/src/cli/handlers/fan-out.ts +53 -6
  455. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  456. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  457. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  458. package/src/cli/handlers/operator.test.ts +37 -0
  459. package/src/cli/handlers/pr.test.ts +341 -0
  460. package/src/cli/handlers/pr.ts +292 -47
  461. package/src/cli/handlers/promote.test.ts +669 -7
  462. package/src/cli/handlers/promote.ts +464 -29
  463. package/src/cli/handlers/release-implicit.e2e.test.ts +161 -0
  464. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  465. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  466. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  467. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  468. package/src/cli/handlers/release.e2e.test.ts +199 -0
  469. package/src/cli/handlers/release.ts +593 -0
  470. package/src/cli/handlers/run-auto-release.test.ts +80 -0
  471. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  472. package/src/cli/handlers/run-release.test.ts +55 -0
  473. package/src/cli/handlers/run.test.ts +30 -0
  474. package/src/cli/handlers/run.ts +243 -18
  475. package/src/cli/main.test.ts +8 -0
  476. package/src/cli/main.ts +143 -17
  477. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  478. package/src/cli/mcp/workspace-tools.ts +1 -1
  479. package/src/cli/parse-args.ts +56 -5
  480. package/src/cli/registry.ts +43 -5
  481. package/src/cli/run-events.ts +57 -0
  482. package/src/cli/version.ts +3 -13
  483. package/src/codegen/docs-in-site.test.ts +166 -0
  484. package/src/codegen/docs-in-site.ts +182 -0
  485. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  486. package/src/codegen/docs-types.ts +14 -1
  487. package/src/codegen/docs.ts +60 -7
  488. package/src/codegen/rehype-base-url.d.mts +3 -1
  489. package/src/codegen/rehype-base-url.mjs +15 -9
  490. package/src/codegen/rehype-base-url.test.ts +34 -0
  491. package/src/components/auto-release.test.ts +30 -0
  492. package/src/components/auto-release.ts +9 -1
  493. package/src/components/capability.ts +9 -0
  494. package/src/components/cli-support.test.ts +82 -0
  495. package/src/components/cli-support.ts +16 -9
  496. package/src/components/driver.test.ts +24 -0
  497. package/src/components/driver.ts +165 -56
  498. package/src/components/fan-out-inputs.test.ts +85 -0
  499. package/src/components/fan-out-inputs.ts +192 -0
  500. package/src/components/fan-out-record.test.ts +10 -0
  501. package/src/components/fan-out-record.ts +19 -0
  502. package/src/components/fan-out-run.ts +104 -35
  503. package/src/components/fan-out.ts +4 -0
  504. package/src/components/pr-pipeline.ts +35 -4
  505. package/src/components/pr-run.ts +30 -1
  506. package/src/components/promote-order.test.ts +90 -0
  507. package/src/components/promote-order.ts +75 -0
  508. package/src/components/promote.test.ts +10 -1
  509. package/src/components/promote.ts +94 -3
  510. package/src/components/release-compile.test.ts +147 -0
  511. package/src/components/release-compile.ts +459 -0
  512. package/src/components/release-gate.test.ts +215 -0
  513. package/src/components/release-gate.ts +355 -0
  514. package/src/components/release-promote.test.ts +159 -0
  515. package/src/components/release-promote.ts +232 -0
  516. package/src/components/run-progress.ts +7 -0
  517. package/src/components/wave-plan.ts +16 -3
  518. package/src/config.ts +15 -1
  519. package/src/flag-params.test.ts +144 -0
  520. package/src/flag-params.ts +134 -0
  521. package/src/index.ts +2 -0
  522. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  523. package/src/lifecycle/change-set-store.test.ts +217 -0
  524. package/src/lifecycle/change-set-store.ts +113 -0
  525. package/src/lifecycle/commit-sha.ts +23 -0
  526. package/src/lifecycle/delivery-metrics.ts +263 -0
  527. package/src/lifecycle/deploy-outcome.ts +147 -0
  528. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  529. package/src/lifecycle/gate-change-set.ts +164 -0
  530. package/src/lifecycle/gate-ledger.test.ts +33 -0
  531. package/src/lifecycle/gate-ledger.ts +68 -3
  532. package/src/lifecycle/git.ts +61 -8
  533. package/src/lifecycle/index.ts +4 -0
  534. package/src/lifecycle/mixed-environment.test.ts +97 -0
  535. package/src/lifecycle/mixed-environment.ts +204 -0
  536. package/src/lifecycle/publish-attachments.ts +196 -0
  537. package/src/lifecycle/publish-credentials.ts +84 -0
  538. package/src/lifecycle/publish-declaration.ts +95 -0
  539. package/src/lifecycle/publish-forge.ts +325 -0
  540. package/src/lifecycle/publish-npm.ts +259 -0
  541. package/src/lifecycle/publish-oci.ts +396 -0
  542. package/src/lifecycle/publish-record.ts +113 -0
  543. package/src/lifecycle/publish-resume.test.ts +168 -0
  544. package/src/lifecycle/publish-targets.test.ts +494 -0
  545. package/src/lifecycle/publish-targets.ts +100 -0
  546. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  547. package/src/lifecycle/release-cut.test.ts +483 -0
  548. package/src/lifecycle/release-cut.ts +503 -0
  549. package/src/lifecycle/release-flag.test.ts +112 -0
  550. package/src/lifecycle/release-join.test.ts +68 -0
  551. package/src/lifecycle/release-join.ts +182 -0
  552. package/src/lifecycle/release-ledger.test.ts +14 -0
  553. package/src/lifecycle/release-ledger.ts +85 -17
  554. package/src/lifecycle/release-publish.test.ts +225 -0
  555. package/src/lifecycle/release-publish.ts +568 -0
  556. package/src/lifecycle/release-revocation.ts +147 -0
  557. package/src/lifecycle/release-store.ts +434 -0
  558. package/src/lifecycle/release-verify.test.ts +110 -0
  559. package/src/lifecycle/release-verify.ts +120 -0
  560. package/src/lifecycle/release-workstation.test.ts +204 -0
  561. package/src/lifecycle/release-workstation.ts +216 -0
  562. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  563. package/src/lifecycle/retention-policy.ts +63 -0
  564. package/src/lifecycle/retention.test.ts +363 -0
  565. package/src/lifecycle/retention.ts +508 -0
  566. package/src/lifecycle/run-ledger.test.ts +10 -0
  567. package/src/lifecycle/run-ledger.ts +6 -0
  568. package/src/op/activities/change-coverage.ts +4 -1
  569. package/src/op/activities/source-release.ts +8 -3
  570. package/src/op/activities/source-rollback.ts +8 -4
  571. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  572. package/src/op/gate-name.ts +13 -3
  573. package/src/op/gate-release.test.ts +126 -0
  574. package/src/op/gate-run-locator.test.ts +110 -0
  575. package/src/op/gate-run-locator.ts +35 -0
  576. package/src/op/gate.ts +88 -6
  577. package/src/op/local-executor.ts +207 -52
  578. package/src/op/op-waves-run.ts +4 -0
  579. package/src/op/run-events.test.ts +166 -0
  580. package/src/op/run-live.ts +1 -1
  581. package/src/op/runtime.ts +39 -0
  582. package/src/op/runtimes/local.test.ts +16 -0
  583. package/src/op/runtimes/local.ts +9 -0
  584. package/src/plan-summary.test.ts +20 -0
  585. package/src/plan-summary.ts +8 -1
  586. package/src/point-decision.ts +12 -2
  587. package/src/pr-loop.ts +25 -4
  588. package/src/pr-report-url.test.ts +131 -0
  589. package/src/pr-report-url.ts +209 -0
  590. package/src/provenance.ts +2 -2
  591. package/src/run-event-store.test.ts +144 -0
  592. package/src/run-event-store.ts +273 -0
  593. package/src/run-event-stream.test.ts +175 -0
  594. package/src/run-event-stream.ts +309 -0
  595. package/src/run-event.test.ts +277 -0
  596. package/src/run-event.ts +471 -0
  597. package/src/wave-loop-golden.test.ts +9 -0
  598. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  599. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  600. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  601. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  602. package/src/workspace/ancestry-index.test.ts +93 -0
  603. package/src/workspace/change-set-show.schema.json +102 -0
  604. package/src/workspace/change-set-show.ts +72 -0
  605. package/src/workspace/change-set.schema.json +32 -1
  606. package/src/workspace/changes-cli.ts +10 -1
  607. package/src/workspace/changes.schema.json +135 -0
  608. package/src/workspace/changes.test.ts +1 -1
  609. package/src/workspace/changes.ts +84 -2
  610. package/src/workspace/check.schema.json +4 -2
  611. package/src/workspace/checks/records.ts +35 -1
  612. package/src/workspace/checks.ts +2 -1
  613. package/src/workspace/ci-green-cli.ts +75 -13
  614. package/src/workspace/ci-green-forge.ts +24 -4
  615. package/src/workspace/ci-green-runs.test.ts +242 -0
  616. package/src/workspace/ci-green-signed.test.ts +302 -0
  617. package/src/workspace/ci-green-workflow.test.ts +16 -0
  618. package/src/workspace/ci-green-workflow.ts +12 -0
  619. package/src/workspace/ci-green.test.ts +131 -2
  620. package/src/workspace/ci-green.ts +635 -42
  621. package/src/workspace/ci-last-green.schema.json +32 -2
  622. package/src/workspace/ci-list.schema.json +525 -0
  623. package/src/workspace/commit-links.test.ts +279 -0
  624. package/src/workspace/commit-links.ts +183 -0
  625. package/src/workspace/declaration.schema.json +76 -0
  626. package/src/workspace/declaration.test.ts +28 -0
  627. package/src/workspace/declaration.ts +60 -2
  628. package/src/workspace/declared-kinds.ts +40 -1
  629. package/src/workspace/delivery-metrics.schema.json +519 -0
  630. package/src/workspace/delivery-metrics.test.ts +236 -0
  631. package/src/workspace/delivery-metrics.ts +286 -0
  632. package/src/workspace/flags-read.ts +121 -0
  633. package/src/workspace/flags.test.ts +141 -0
  634. package/src/workspace/flags.ts +157 -0
  635. package/src/workspace/graph-cli.ts +1 -1
  636. package/src/workspace/identity-gate-rule.ts +91 -7
  637. package/src/workspace/identity.ts +14 -1
  638. package/src/workspace/intent-cli.ts +46 -5
  639. package/src/workspace/intent-trace.test.ts +173 -0
  640. package/src/workspace/intent-trace.ts +484 -0
  641. package/src/workspace/intent.schema.json +702 -6
  642. package/src/workspace/intent.ts +196 -26
  643. package/src/workspace/ls.schema.json +3 -1
  644. package/src/workspace/ls.ts +6 -0
  645. package/src/workspace/member-pipeline.test.ts +17 -0
  646. package/src/workspace/member-pipeline.ts +9 -0
  647. package/src/workspace/older-records.test.ts +412 -0
  648. package/src/workspace/overlap.test.ts +165 -0
  649. package/src/workspace/overlap.ts +340 -0
  650. package/src/workspace/promotion-order.ts +97 -0
  651. package/src/workspace/read-contract.e2e.test.ts +5 -1
  652. package/src/workspace/reason-codes.test.ts +7 -1
  653. package/src/workspace/reason-codes.ts +7 -1
  654. package/src/workspace/record-assets.test.ts +2 -1
  655. package/src/workspace/records-contract.e2e.test.ts +2 -1
  656. package/src/workspace/records.schema.json +21 -2
  657. package/src/workspace/records.ts +36 -2
  658. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  659. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  660. package/src/workspace/releases.schema.json +513 -0
  661. package/src/workspace/releases.test.ts +108 -0
  662. package/src/workspace/releases.ts +133 -0
  663. package/src/workspace/run-event.schema.json +295 -0
  664. package/src/workspace/status-contract.test.ts +81 -1
  665. package/src/workspace/status-gates.ts +64 -1
  666. package/src/workspace/status-mixed.test.ts +152 -0
  667. package/src/workspace/status-revoked.test.ts +153 -0
  668. package/src/workspace/status-stewards.ts +6 -0
  669. package/src/workspace/status.schema.json +268 -2
  670. package/src/workspace/status.ts +334 -4
  671. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  672. package/src/workspace/trust/op-run-statement.ts +148 -0
  673. package/src/workspace/trust/rotation.ts +61 -14
  674. package/src/workspace/trust/seal.ts +10 -0
  675. package/src/workspace/trust/ssh-commit.ts +10 -9
  676. package/src/workspace/wip.ts +11 -2
  677. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  678. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  679. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  680. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  681. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -0,0 +1,166 @@
1
+ /**
2
+ * #4041: the local executor's run-event stream (`chant run <op>
3
+ * --events-json`). Every line passes the schema; each step's start comes
4
+ * before its settle under the same step id; and the cases an old
5
+ * `StepRecord` could not tell apart come out as `gated`, `skip: "receipt"`
6
+ * and `skip: "not-run"`.
7
+ */
8
+
9
+ import { describe, expect, test, vi } from "vitest";
10
+ import type { ActivityFn, ActivityProfile } from "./activity-registry";
11
+ import type { OpConfig } from "./types";
12
+ import { OpRunFailure, runOpLocally, type RunOpOptions } from "./local-executor";
13
+ import { memoryGateLedgerPort } from "./gate";
14
+ import { receiptActivities, type ReceiptStore } from "./receipt-store";
15
+ import { EffectReceipt, receiptExpectation } from "../effect-receipt";
16
+ import { effect } from "./builders";
17
+ import type { RunEvent } from "../run-event";
18
+ import { checkRunEventLines, stepKey, stepTrail } from "../workspace/__fixtures__/run-event-check";
19
+
20
+ vi.mock("./gate", async () => {
21
+ const actual = await vi.importActual<typeof import("./gate")>("./gate");
22
+ return { ...actual, gateIsSealed: async () => false };
23
+ });
24
+
25
+ const PROFILES: Record<string, ActivityProfile> = { fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } } };
26
+ const NOW = "2026-10-16T12:00:00.000Z";
27
+ const SHA = "0123456789abcdef0123456789abcdef01234567";
28
+
29
+ const op = (partial: Partial<OpConfig>): OpConfig => ({ name: "deploy-api", overview: "", phases: [], ...partial });
30
+ const ok: ActivityFn = async () => ({ done: true });
31
+
32
+ /** Run an Op with its events captured, settling whether it resolves or rejects. */
33
+ async function run(config: OpConfig, activities: Map<string, ActivityFn>, options: RunOpOptions = {}) {
34
+ const lines: string[] = [];
35
+ let status: string;
36
+ try {
37
+ status = (await runOpLocally(config, activities, PROFILES, undefined, {
38
+ gates: memoryGateLedgerPort(),
39
+ now: NOW,
40
+ runId: "local-1",
41
+ commit: SHA,
42
+ runEvents: { write: (l) => lines.push(l) },
43
+ ...options,
44
+ })).status;
45
+ } catch (err) {
46
+ if (!(err instanceof OpRunFailure)) throw err;
47
+ status = err.result.status;
48
+ }
49
+ return { status, events: checkRunEventLines(lines) };
50
+ }
51
+
52
+ describe("the local runtime's run events", () => {
53
+ test("a run that passes: run, phases and steps, each step started before it settles, named as the run record names it", async () => {
54
+ const { status, events } = await run(
55
+ op({ phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }] }, { name: "Ship", steps: [{ kind: "activity", fn: "push" }] }] }),
56
+ new Map([["make", ok], ["push", ok]]),
57
+ );
58
+ expect(status).toBe("ok");
59
+ // In a CI job the run keeps its own id and names the CI run as its origin, as its run record does (#4020).
60
+ const ci = Boolean(process.env.GITHUB_RUN_ID || process.env.CI_PIPELINE_ID);
61
+ expect(events[0]).toMatchObject({ runId: "local-1", kind: "op", name: "deploy-api", gitSha: SHA, runtime: ci ? "ci" : "local" });
62
+ expect(events[0]!.runOrigin.forge).toBe(process.env.GITHUB_RUN_ID ? "github" : process.env.CI_PIPELINE_ID ? "gitlab" : "local");
63
+ expect(events.map((e) => e.type)).toEqual([
64
+ "run-start",
65
+ "phase-start", "step-start", "step-settle", "phase-settle",
66
+ "phase-start", "step-start", "step-settle", "phase-settle",
67
+ "run-settle",
68
+ ]);
69
+ expect(events.at(-1)).toMatchObject({ status: "ok" });
70
+ });
71
+
72
+ test("a step's start is emitted before its settle, with the same step id", async () => {
73
+ const { events } = await run(
74
+ op({ phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }, { kind: "activity", fn: "make" }] }] }),
75
+ new Map([["make", ok]]),
76
+ );
77
+ const steps = events.filter((e): e is Extract<RunEvent, { type: "step-start" | "step-settle" }> => e.type === "step-start" || e.type === "step-settle");
78
+ // Two steps of one phase share a name; the index keeps them apart.
79
+ expect(steps.map((e) => [e.type, stepKey(e)])).toEqual([
80
+ ["step-start", JSON.stringify([null, "Build", "make", 0])],
81
+ ["step-settle", JSON.stringify([null, "Build", "make", 0])],
82
+ ["step-start", JSON.stringify([null, "Build", "make", 1])],
83
+ ["step-settle", JSON.stringify([null, "Build", "make", 1])],
84
+ ]);
85
+ expect(steps[0]!.seq).toBeLessThan(steps[1]!.seq);
86
+ });
87
+
88
+ test("a parallel phase starts each step before any settles, and tells two steps of one name apart by index", async () => {
89
+ const { events } = await run(
90
+ op({ phases: [{ name: "Fan", parallel: true, steps: [{ kind: "activity", fn: "probe", args: { at: 1 } }, { kind: "activity", fn: "probe", args: { at: 2 } }] }] }),
91
+ new Map([["probe", ok]]),
92
+ );
93
+ expect(stepTrail(events)).toEqual([
94
+ ["step-start", "probe"], ["step-start", "probe"], ["step-settle", "probe", "ok"], ["step-settle", "probe", "ok"],
95
+ ]);
96
+ expect(events.filter((e) => e.type === "step-settle").map((e) => (e as { index?: number }).index)).toEqual([0, 1]);
97
+ });
98
+
99
+ test("an unapproved gate settles gated after gate-waiting; the steps after it settle skipped as not-run", async () => {
100
+ const { status, events } = await run(
101
+ op({ phases: [{ name: "Ship", steps: [{ kind: "activity", fn: "before" }, { kind: "gate", gate: "approve-prod" }, { kind: "activity", fn: "after" }] }] }),
102
+ new Map([["before", ok], ["after", ok]]),
103
+ );
104
+ expect(status).toBe("gated");
105
+ expect(stepTrail(events)).toEqual([
106
+ ["step-start", "before"], ["step-settle", "before", "ok"],
107
+ ["step-start", "gate:approve-prod"], ["gate-waiting", "gate:approve-prod"], ["step-settle", "gate:approve-prod", "gated"],
108
+ ["step-settle", "after", "skipped:not-run"],
109
+ ]);
110
+ expect(events.find((e) => e.type === "gate-waiting")).toMatchObject({ op: "deploy-api", gate: "approve-prod", point: null, index: 1 });
111
+ expect(events.find((e) => e.type === "phase-settle")).toMatchObject({ status: "gated" });
112
+ expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "gated" });
113
+ });
114
+
115
+ test("an approved gate resolves, naming who approved it", async () => {
116
+ const gates = memoryGateLedgerPort({
117
+ resolutions: [{ version: 1, op: "deploy-api", gate: "approve-prod", resolvedBy: "alex", timestamp: "2026-10-16T11:00:00.000Z" }],
118
+ });
119
+ const { status, events } = await run(op({ phases: [{ name: "Ship", steps: [{ kind: "gate", gate: "approve-prod" }] }] }), new Map(), { gates });
120
+ expect(status).toBe("ok");
121
+ expect(events.find((e) => e.type === "gate-resolved")).toMatchObject({ gate: "approve-prod", resolvedBy: "alex", via: null, index: 0 });
122
+ });
123
+
124
+ test("a failed step fails its phase and the run, with its error, and the rest of its phase is not-run", async () => {
125
+ const boom: ActivityFn = async () => { throw new Error("disk full"); };
126
+ const { status, events } = await run(
127
+ op({ phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }, { kind: "activity", fn: "pack" }] }] }),
128
+ new Map([["make", boom], ["pack", ok]]),
129
+ );
130
+ expect(status).toBe("fail");
131
+ expect(stepTrail(events)).toEqual([["step-start", "make"], ["step-settle", "make", "failed"], ["step-settle", "pack", "skipped:not-run"]]);
132
+ expect(events.find((e) => e.type === "phase-settle")).toMatchObject({ status: "failed" });
133
+ expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "failed", error: "disk full" });
134
+ });
135
+
136
+ test("an effect whose receipt matched settles its steps skipped with skip: receipt", async () => {
137
+ const seeded = EffectReceipt("seeded", { effect: "db-seed", flavor: "hash", inputs: { version: 3 } });
138
+ const values = new Map<string, string>([["seeded", receiptExpectation(seeded)]]);
139
+ const store: ReceiptStore = {
140
+ read: async (r) => values.get(r.name),
141
+ write: async (r, e) => void values.set(r.name, e),
142
+ };
143
+ const { receiptRead, receiptWrite } = receiptActivities(store);
144
+ const { status, events } = await run(
145
+ op({ phases: [{ name: "Seed", steps: [effect(seeded, [{ kind: "activity", fn: "seed" }])] }] }),
146
+ new Map<string, ActivityFn>([["seed", ok], ["receiptRead", receiptRead as unknown as ActivityFn], ["receiptWrite", receiptWrite as unknown as ActivityFn]]),
147
+ );
148
+ expect(status).toBe("ok");
149
+ expect(stepTrail(events)).toEqual([
150
+ ["step-start", "receiptRead"], ["step-settle", "receiptRead", "ok"],
151
+ ["step-settle", "seed", "skipped:receipt"],
152
+ ]);
153
+ });
154
+
155
+ test("a run that throws before its first phase still ends its stream, failed with the reason", async () => {
156
+ const lines: string[] = [];
157
+ await expect(
158
+ runOpLocally(op({ phases: [{ name: "P", parallel: true, steps: [effect(EffectReceipt("r", { effect: "e", flavor: "existence", inputs: {} }), [])] }] }), new Map(), PROFILES, undefined, {
159
+ gates: memoryGateLedgerPort(), now: NOW, commit: SHA, runEvents: { write: (l) => lines.push(l) },
160
+ }),
161
+ ).rejects.toThrow(/cannot run in a parallel phase/);
162
+ const events = checkRunEventLines(lines);
163
+ expect(events.map((e) => e.type)).toEqual(["run-start", "run-settle"]);
164
+ expect(events.at(-1)).toMatchObject({ status: "failed", error: expect.stringMatching(/cannot run in a parallel phase/) });
165
+ });
166
+ });
@@ -86,7 +86,7 @@ function keyOf(prefix: string, env: string, op: string): string {
86
86
  }
87
87
 
88
88
  /** The checkout's shared git directory (the same for every worktree), or null outside a checkout. */
89
- async function gitCommonDir(cwd: string): Promise<string | null> {
89
+ export async function gitCommonDir(cwd: string): Promise<string | null> {
90
90
  try {
91
91
  const { stdout } = await execFileAsync("git", ["rev-parse", "--git-common-dir"], { cwd });
92
92
  const dir = stdout.trim();
package/src/op/runtime.ts CHANGED
@@ -20,6 +20,7 @@ import type { OpConfig } from "./types";
20
20
  import type { StepRecord, OpRunResult } from "./local-executor";
21
21
  import type { WaitingPoint } from "./steward-points";
22
22
  import type { GateResolutionRecord } from "../lifecycle/gate-ledger";
23
+ import type { RunOrigin } from "../lifecycle/release-ledger";
23
24
  import type { RunComponentsOptions, RunComponentsResult } from "../components/cli-support";
24
25
 
25
26
  /**
@@ -159,6 +160,29 @@ export interface OpRunRecord {
159
160
  * first phase.
160
161
  */
161
162
  resumed?: { gate: string; run: string; checkpoint: string };
163
+ /**
164
+ * The commit the run built from (#4020): the checkout's `HEAD`, else the
165
+ * commit the CI job started for. Gate facts the run writes carry the same
166
+ * commit, so a run and its gate join on it. Absent on a record written
167
+ * before 0.124.28 and when the run knew no commit.
168
+ */
169
+ gitSha?: string;
170
+ /**
171
+ * The CI run this Op ran in (#4020), as a deploy record's `runOrigin` names
172
+ * its run, so the two name a run the same way. `id` is still the Op's own
173
+ * run id. Absent outside CI and on a record written before 0.124.28.
174
+ */
175
+ runOrigin?: RunOrigin;
176
+ /**
177
+ * The run's statement (#4044): a DSSE envelope over an in-toto statement
178
+ * (`https://intentius.io/chant/op-run/v1`) a runner key signed at the end
179
+ * of the run, naming the run, its commit and the SHA-256 of this record
180
+ * without the statement (`../workspace/trust/op-run-statement.ts`).
181
+ * `chant ci tick` with `ci.green.source: "runs"` counts only a run that
182
+ * carries one. Absent on a run given no runner key, and on a record
183
+ * written before 0.124.28.
184
+ */
185
+ statement?: { payloadType: string; payload: string; signatures: { keyid: string; sig: string }[] };
162
186
  }
163
187
 
164
188
  /** An {@link OpRunRecord} before the ledger stamps its version and mints an id. */
@@ -187,6 +211,14 @@ export interface OpRunStartOptions {
187
211
  profile?: string;
188
212
  /** Called once per settled step, so `--progress-json` can stream. */
189
213
  progress?: (record: StepRecord) => void;
214
+ /**
215
+ * Where the run's event stream goes (#4041, `--events-json`): one
216
+ * `run-event.schema.json` line at a time, written through the one writer
217
+ * (`createRunEventWriter`). The local runtime writes `runtime: "local"`
218
+ * events (`ci` in a CI job); a hosted runtime writes its own, as fountain
219
+ * writes `runtime: "steward"` ones. A runtime that cannot stream omits it.
220
+ */
221
+ runEvents?: (line: string) => void;
190
222
  /** Aborts in-flight work (Ctrl-C). */
191
223
  signal?: AbortSignal;
192
224
  /**
@@ -205,6 +237,13 @@ export interface OpRunStartOptions {
205
237
  * and the run `chant run status --on fountain` reports carry the same id.
206
238
  */
207
239
  runId?: string;
240
+ /**
241
+ * Signs the run's record when it settles (#4044), for `chant run <op>
242
+ * --key` or `CHANT_RUNNER_KEY`: the local runtime stores what it returns
243
+ * on the record as `statement`. A runtime that writes no record here
244
+ * ignores it.
245
+ */
246
+ signRecord?: (record: OpRunRecord) => NonNullable<OpRunRecord["statement"]>;
208
247
  }
209
248
 
210
249
  /** What a caller hands {@link OpRuntimeProvider.resolveGate} besides the resolution. */
@@ -51,6 +51,7 @@ vi.mock("../../lifecycle/run-ledger", async () => {
51
51
  });
52
52
 
53
53
  const { createLocalOpRuntime } = await import("./local");
54
+ const { checkRunEventLines, stepTrail } = await import("../../workspace/__fixtures__/run-event-check");
54
55
 
55
56
  function op(name: string, steps: unknown[]): OpConfig {
56
57
  return {
@@ -145,6 +146,21 @@ describe("the local op runtime", () => {
145
146
  expect(seen).toEqual(["ok", "ok"]);
146
147
  });
147
148
 
149
+ test("runEvents gets the run's event stream, named by the handle's run id (#4041)", async () => {
150
+ loadActivitiesMock.mockResolvedValue(new Map([["ok", vi.fn(async () => ({}))]]));
151
+ const runtime = createLocalOpRuntime();
152
+ const lines: string[] = [];
153
+
154
+ const handle = await runtime.start(op("hello", [{ kind: "activity", fn: "ok", args: {} }]), { runEvents: (l) => lines.push(l) });
155
+ await handle.result();
156
+
157
+ const events = checkRunEventLines(lines);
158
+ expect(events[0]).toMatchObject({ type: "run-start", runId: handle.runId, kind: "op", name: "hello" });
159
+ expect(events[0]!.runtime).toBe(process.env.GITHUB_RUN_ID || process.env.CI_PIPELINE_ID ? "ci" : "local");
160
+ expect(stepTrail(events)).toEqual([["step-start", "ok"], ["step-settle", "ok", "ok"]]);
161
+ expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "ok" });
162
+ });
163
+
148
164
  test("a gated Op settles as gated, naming the gate, and runs nothing after it (#2119)", async () => {
149
165
  const after = vi.fn(async () => ({}));
150
166
  loadActivitiesMock.mockResolvedValue(new Map([["after", after]]));
@@ -176,6 +176,15 @@ export function createLocalOpRuntime(opts: { projectPath?: string } = {}): OpRun
176
176
  `appended to the run ledger: ${err instanceof Error ? err.message : String(err)}\n`,
177
177
  ),
178
178
  ...(startOpts.progress ? { onRecord: startOpts.progress } : {}),
179
+ ...(startOpts.runEvents ? { runEvents: { write: startOpts.runEvents } } : {}),
180
+ // #4044: a runner key signs the record as the run settles.
181
+ ...(startOpts.signRecord
182
+ ? {
183
+ signRecord: startOpts.signRecord,
184
+ onSignError: (err: unknown) =>
185
+ process.stderr.write(`warning: run "${runId}" of "${op.name}" was not signed: ${err instanceof Error ? err.message : String(err)}\n`),
186
+ }
187
+ : {}),
179
188
  // #3956: a running step's output, and a still-running line while
180
189
  // it is quiet, on stderr as they happen; stdout stays the
181
190
  // `--json` record's and `--progress-json`'s.
@@ -261,3 +261,23 @@ describe("addresses", () => {
261
261
  expect(escapeMarkerAddress('aws_s3_bucket.logs["a*b"]')).toBe("aws_s3_bucket.logs:a+00002Ab");
262
262
  });
263
263
  });
264
+
265
+ describe("a no-change group with holes (#4084)", () => {
266
+ const part = (member: string, holes: ChangeSetPart["member"]["holes"] = []): ChangeSetPart => ({
267
+ member: { member, lexicon: "aws", planner: "chant", status: "planned", planDigest: `jcs1-sha256:${member}`, holes },
268
+ entries: [],
269
+ });
270
+ const hole = { address: "cluster-a", type: "AWS::CloudFormation::Stack", reason: "inputs from network; re-checked on apply" };
271
+
272
+ test("does not say no changes for a member whose plan has holes, and keeps the group and its id", () => {
273
+ const withHole = groupChangeSet(composeChangeSet([part("app-01"), part("app-02"), part("cluster-a", [hole])]));
274
+ const without = groupChangeSet(composeChangeSet([part("app-01"), part("app-02"), part("cluster-a")]));
275
+ expect(withHole.groups.map((g) => [g.id, g.units, g.noChanges])).toEqual(without.groups.map((g) => [g.id, g.units, g.noChanges]));
276
+ const id = withHole.groups[0].id;
277
+ const text = renderPlanSummaryText(withHole);
278
+ expect(text).toContain(`Group ${id}: 3 members, no changes known: 1 with holes, listed above:`);
279
+ expect(text).toContain("cluster-a: cluster-a: inputs from network; re-checked on apply");
280
+ expect(renderPlanSummaryMarkdown(withHole)).toContain(`#### Group ${id}: 3 members, no changes known: 1 with holes, listed above`);
281
+ expect(renderPlanSummaryText(without)).toContain(`Group ${id}: 3 members, no changes:`);
282
+ });
283
+ });
@@ -689,7 +689,14 @@ function groupTitle(s: PlanSummary, g: PlanSummaryGroup): string {
689
689
  if (g.resource) t += ` of ${g.resource}`;
690
690
  if (g.outlier) t += " (outlier)";
691
691
  if (g.provisional) t += " (provisional)";
692
- if (g.noChanges) t += ", no changes";
692
+ if (g.noChanges) {
693
+ // A member with holes planned no entries, but its plan says nothing about
694
+ // what the holes cover, so the group cannot say "no changes" for it
695
+ // (#4084). Only the heading says so: the group and its id are unchanged.
696
+ const holed = s.unit === "member" ? new Set(s.holes.map((h) => h.member)) : new Set<string>();
697
+ const unknown = g.units.filter((u) => holed.has(u)).length;
698
+ t += unknown > 0 ? `, no changes known: ${unknown} with holes, listed above` : ", no changes";
699
+ }
693
700
  else if (g.extends) t += `, group ${g.extends}'s change plus`;
694
701
  else if (g.units.length > 1) t += ", identical change";
695
702
  else t += ", change";
@@ -36,7 +36,7 @@ import { parseDuration } from "./op/duration";
36
36
  import type { GateLedgerPort } from "./op/gate";
37
37
  import { DEFAULT_GATE_POINT_PASS, defaultGatePointStore, gatePointInputs, gateSubject, type GatePoint, type GatePointFacts, type GatePointStore } from "./op/gate-point";
38
38
  import type { WaitingPoint } from "./op/steward-points";
39
- import { resolveGateRunLocator, type GateRunLocator } from "./op/gate-run-locator";
39
+ import { ciCommitFromEnv, ciRunIdFromEnv, resolveGateRunLocator, type GateRunLocator } from "./op/gate-run-locator";
40
40
  import { refuseGateWithoutRecording } from "./lifecycle/no-record";
41
41
 
42
42
  export {
@@ -102,6 +102,13 @@ export interface GatePointAsker {
102
102
  ask(request: GatePointRequest): Promise<GatePointQuestion>;
103
103
  }
104
104
 
105
+ /** The run and commit a gate record names (#4020): the input's own, else the CI job's. */
106
+ function runStamp(input: { runId?: string; gitSha?: string }): { runId?: string; gitSha?: string } {
107
+ const runId = input.runId || ciRunIdFromEnv();
108
+ const gitSha = input.gitSha || ciCommitFromEnv();
109
+ return { ...(runId ? { runId } : {}), ...(gitSha ? { gitSha } : {}) };
110
+ }
111
+
105
112
  /** What the run needs to decide a gate that asks a point. */
106
113
  export interface PointGateInput {
107
114
  op: string;
@@ -112,6 +119,8 @@ export interface PointGateInput {
112
119
  description?: string;
113
120
  timeout?: string;
114
121
  runId?: string;
122
+ /** The commit the run is at (#4020). Default: the commit the CI job started for, when it runs in CI. */
123
+ gitSha?: string;
115
124
  planDigest?: string;
116
125
  /** The environment the run was started for: the `gate.env` input. Op gate facts on the ledger record none. */
117
126
  env?: string;
@@ -202,6 +211,7 @@ export async function evaluatePointGate(port: PointGateLedgerPort, asker: GatePo
202
211
  resolvedBy: resolvedByOf(q),
203
212
  timestamp: now,
204
213
  note: `answered through decision point ${q.point} (${q.id})`,
214
+ ...runStamp(input),
205
215
  ...(input.planDigest !== undefined ? { planDigest: input.planDigest } : {}),
206
216
  approver: { kind: q.decider === "quorum" || q.answeredBy.length > 0 ? "human" : "agent" },
207
217
  answer,
@@ -228,7 +238,7 @@ export async function evaluatePointGate(port: PointGateLedgerPort, asker: GatePo
228
238
  timestamp: now,
229
239
  expiresAt: new Date(new Date(now).getTime() + parseDuration(input.timeout ?? DEFAULT_GATE_EXPIRY)).toISOString(),
230
240
  ...(input.description ? { description: input.description } : {}),
231
- ...(input.runId ? { runId: input.runId } : {}),
241
+ ...runStamp(input),
232
242
  ...(url ? { url } : {}),
233
243
  ...(input.planDigest !== undefined ? { planDigest: input.planDigest } : {}),
234
244
  ...(resume ? { resume } : {}),
package/src/pr-loop.ts CHANGED
@@ -204,6 +204,17 @@ export function memberCounts(doc: Pick<ChangeSetDocument, "summary">, member: st
204
204
  return { create: n("create"), update: n("update"), replace: n("replace"), delete: n("delete") };
205
205
  }
206
206
 
207
+ /**
208
+ * Why a member's plan cannot say everything it will change: its holes'
209
+ * reasons, each once, in order (#4084). A `cfn-deploy` member whose inputs
210
+ * come from a stack the same run applies first says `inputs from <stack>;
211
+ * re-checked on apply`.
212
+ */
213
+ export function memberHoleReasons(report: Pick<PrReport, "changeSet">, member: string): string[] {
214
+ const m = report.changeSet.members.find((x) => x.member === member);
215
+ return [...new Set((m?.holes ?? []).map((h) => h.reason))];
216
+ }
217
+
207
218
  /** The approve command a reviewer runs for this report's digest. */
208
219
  export function prApproveCommand(report: Pick<PrReport, "op" | "gate" | "digest">, approver = "<you>"): string {
209
220
  return `chant approve ${report.op} ${report.gate} --plan ${report.digest} --approver ${approver} --sign`;
@@ -295,8 +306,16 @@ export function renderPrNote(report: PrReport, options: { limit?: number; approv
295
306
  if (report.members.length > 0) {
296
307
  out.push("", "| Member | Component | Status | Create | Update | Replace | Delete |", "|---|---|---|---:|---:|---:|---:|");
297
308
  for (const m of report.members) {
298
- const status = STATUS_WORDS[m.status] + (m.inputsMoved?.length ? `, inputs from ${m.inputsMoved.join(", ")} moved` : "");
299
- out.push(`| ${code(m.member)} | ${m.component} | ${status} | ${m.counts.create} | ${m.counts.update} | ${m.counts.replace} | ${m.counts.delete} |`);
309
+ const holes = memberHoleReasons(report, m.member);
310
+ const status =
311
+ STATUS_WORDS[m.status] +
312
+ (m.inputsMoved?.length ? `, inputs from ${m.inputsMoved.join(", ")} moved` : "") +
313
+ (holes.length > 0 ? `; ${holes.length > 2 ? `${holes[0]}; and ${holes.length - 1} more, listed under Holes` : holes.join("; ")}` : "");
314
+ // A member whose plan has holes and no entries says nothing about what
315
+ // it changes, so its counts are unknown, not zero (#4084).
316
+ const unknown = holes.length > 0 && !report.changeSet.entries.some((e) => e.member === m.member);
317
+ const n = (k: keyof PrMemberCounts): string => (unknown ? "?" : String(m.counts[k]));
318
+ out.push(`| ${code(m.member)} | ${m.component} | ${status} | ${n("create")} | ${n("update")} | ${n("replace")} | ${n("delete")} |`);
300
319
  }
301
320
  if (report.members.some((m) => m.inputsMoved?.length)) {
302
321
  out.push("", "A member whose inputs moved applied the plan it was approved with. The next run plans it against the new values.");
@@ -328,9 +347,11 @@ export function prStatusDescription(report: PrReport): string {
328
347
  case "nothing":
329
348
  text = "nothing to plan";
330
349
  break;
331
- case "planned":
332
- text = `${n} member${n === 1 ? "" : "s"}: ${sum.change} to change, ${sum.destroy} to destroy or replace${digest}`;
350
+ case "planned": {
351
+ const unknown = report.members.filter((m) => memberHoleReasons(report, m.member).length > 0).length;
352
+ text = `${n} member${n === 1 ? "" : "s"}: ${sum.change} to change, ${sum.destroy} to destroy or replace${unknown > 0 ? `, ${unknown} with holes` : ""}${digest}`;
333
353
  break;
354
+ }
334
355
  case "plan-failed":
335
356
  text = `a member failed to plan${digest}`;
336
357
  break;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * `--report-url` (#4007): the URL forms, the key, and the upload of a run's
3
+ * events as it goes: one upload in flight at a time, a failure that warns
4
+ * and never stops the run, and a last upload that carries everything. The
5
+ * s3 scheme runs a fake AWS CLI; nothing here reaches a network.
6
+ */
7
+
8
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { pathToFileURL } from "node:url";
12
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
13
+ import { openPrReportUpload, parseReportUrl, reportKey, reportPut, type ReportPut } from "./pr-report-url";
14
+
15
+ const line = (type: string, extra: Record<string, unknown> = {}): string => JSON.stringify({ type, ...extra }) + "\n";
16
+
17
+ let dir: string;
18
+ beforeEach(async () => {
19
+ dir = await mkdtemp(join(tmpdir(), "pr-report-url-"));
20
+ });
21
+ afterEach(async () => {
22
+ await rm(dir, { recursive: true, force: true });
23
+ });
24
+
25
+ describe("parseReportUrl", () => {
26
+ test("reads s3 and file URLs and refuses anything else", () => {
27
+ expect(parseReportUrl("s3://bucket/a/b/")).toEqual({ scheme: "s3", url: "s3://bucket/a/b", bucket: "bucket", prefix: "a/b" });
28
+ expect(parseReportUrl("s3://bucket")).toEqual({ scheme: "s3", url: "s3://bucket", bucket: "bucket", prefix: "" });
29
+ expect(parseReportUrl(pathToFileURL(dir).href)).toMatchObject({ scheme: "file", dir });
30
+ expect(parseReportUrl("https://example.invalid/x")).toHaveProperty("error");
31
+ });
32
+
33
+ test("keys by pull request and commit, and by member after them", () => {
34
+ expect(reportKey(12, "abc")).toBe("12/abc");
35
+ expect(reportKey(null, "abc")).toBe("unknown/abc");
36
+ expect(reportKey(12, "abc", "api")).toBe("12/abc/api");
37
+ });
38
+ });
39
+
40
+ describe("openPrReportUpload", () => {
41
+ /** A put whose uploads finish only when the test says, recording what each carried. */
42
+ function heldPut() {
43
+ const calls: Array<{ key: string; body: string; finish: (err?: Error) => void }> = [];
44
+ let inFlight = 0;
45
+ let most = 0;
46
+ const put: ReportPut = (key, body) =>
47
+ new Promise<void>((resolve, reject) => {
48
+ inFlight++;
49
+ most = Math.max(most, inFlight);
50
+ calls.push({ key, body, finish: (err) => { inFlight--; if (err) reject(err); else resolve(); } });
51
+ });
52
+ return { put, calls, most: () => most };
53
+ }
54
+
55
+ test("uploads the events on each settle, never two at once, and the settles that came meanwhile in one more", async () => {
56
+ const held = heldPut();
57
+ const upload = openPrReportUpload({ target: { scheme: "s3", url: "s3://b", bucket: "b", prefix: "" }, key: "12/abc", stage: "apply", warn: () => {}, put: held.put });
58
+ upload.line(line("run-start"));
59
+ expect(held.calls).toHaveLength(0);
60
+ upload.line(line("wave-start", { wave: 1 }));
61
+ upload.line(line("component-settle", { wave: 1 }));
62
+ expect(held.calls).toHaveLength(1);
63
+ upload.line(line("wave-settle", { wave: 1 }));
64
+ upload.line(line("wave-start", { wave: 2 }));
65
+ upload.line(line("component-settle", { wave: 2 }));
66
+ // Both settles wait behind the one in flight, and go up as one.
67
+ expect(held.calls).toHaveLength(1);
68
+ held.calls[0]!.finish();
69
+ await new Promise((r) => setImmediate(r));
70
+ expect(held.calls).toHaveLength(2);
71
+ expect(held.calls[1]!.body.split("\n").filter(Boolean)).toHaveLength(6);
72
+ expect(held.calls[1]!.key).toBe("12/abc/pr-apply.events.jsonl");
73
+
74
+ upload.file("pr-apply.json", "{}\n");
75
+ const closing = upload.close();
76
+ await new Promise((r) => setImmediate(r));
77
+ // The last upload waits for the one in flight.
78
+ expect(held.calls).toHaveLength(2);
79
+ held.calls[1]!.finish();
80
+ await new Promise((r) => setImmediate(r));
81
+ held.calls[2]!.finish();
82
+ await new Promise((r) => setImmediate(r));
83
+ held.calls[3]!.finish();
84
+ await closing;
85
+ expect(held.calls.map((c) => c.key)).toEqual(["12/abc/pr-apply.events.jsonl", "12/abc/pr-apply.events.jsonl", "12/abc/pr-apply.events.jsonl", "12/abc/pr-apply.json"]);
86
+ expect(held.most()).toBe(1);
87
+ });
88
+
89
+ test("a failed upload as the run goes warns once and the run goes on; the last one is tried again", async () => {
90
+ const warnings: string[] = [];
91
+ let fail = true;
92
+ const put: ReportPut = async (key) => {
93
+ if (fail) throw new Error(`no bucket for ${key}`);
94
+ };
95
+ const upload = openPrReportUpload({ target: { scheme: "s3", url: "s3://b", bucket: "b", prefix: "" }, key: "12/abc", stage: "apply", warn: (m) => warnings.push(m), put });
96
+ upload.line(line("component-settle"));
97
+ await new Promise((r) => setImmediate(r));
98
+ upload.line(line("wave-settle"));
99
+ await new Promise((r) => setImmediate(r));
100
+ expect(warnings).toHaveLength(1);
101
+ expect(warnings[0]).toMatch(/as the run went: no bucket .*The run goes on/);
102
+ fail = false;
103
+ await upload.close();
104
+ expect(warnings).toHaveLength(1);
105
+ });
106
+
107
+ test("file://: each settle replaces the events file whole, so it holds a wave's settle before the next wave starts", async () => {
108
+ const target = parseReportUrl(pathToFileURL(dir).href);
109
+ if ("error" in target) throw new Error(target.error);
110
+ const upload = openPrReportUpload({ target, key: "12/abc", stage: "apply", warn: () => {} });
111
+ upload.line(line("run-start"));
112
+ upload.line(line("wave-settle", { wave: 1 }));
113
+ await new Promise((r) => setImmediate(r));
114
+ const events = join(dir, "12", "abc", "pr-apply.events.jsonl");
115
+ expect(await readFile(events, "utf8")).toBe(line("run-start") + line("wave-settle", { wave: 1 }));
116
+ upload.line(line("run-settle"));
117
+ upload.file("change-set.json", "{}\n");
118
+ await upload.close();
119
+ expect((await readFile(events, "utf8")).split("\n").filter(Boolean)).toHaveLength(3);
120
+ expect(await readFile(join(dir, "12", "abc", "change-set.json"), "utf8")).toBe("{}\n");
121
+ });
122
+
123
+ test("s3://: the AWS CLI copies each object from its stdin, with the content type, and chant hands it no credential", async () => {
124
+ const runs: Array<{ args: string[]; stdin: string }> = [];
125
+ const put = reportPut({ scheme: "s3", url: "s3://b/p", bucket: "b", prefix: "p" }, async (args, stdin) => {
126
+ runs.push({ args, stdin });
127
+ });
128
+ await put("12/abc/pr-plan.json", "{}\n", "application/json");
129
+ expect(runs).toEqual([{ args: ["s3", "cp", "-", "s3://b/p/12/abc/pr-plan.json", "--content-type", "application/json", "--only-show-errors"], stdin: "{}\n" }]);
130
+ });
131
+ });