@intentius/chant 0.124.26 → 0.124.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (459) hide show
  1. package/README.md +1 -1
  2. package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
  3. package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
  4. package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  6. package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
  7. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  8. package/dist/__golden__/wave-loop/op-held-on.json +2 -0
  9. package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
  10. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  11. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  12. package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
  13. package/dist/__golden__/wave-loop/op-per-run.json +7 -0
  14. package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
  15. package/dist/__golden__/wave-loop/op-sealed.json +3 -0
  16. package/dist/__golden__/wave-loop/op-spend.json +3 -0
  17. package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
  18. package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
  19. package/dist/change-set-graph.d.ts +56 -0
  20. package/dist/change-set-graph.d.ts.map +1 -0
  21. package/dist/change-set-graph.js +128 -0
  22. package/dist/change-set.d.ts +52 -3
  23. package/dist/change-set.d.ts.map +1 -1
  24. package/dist/change-set.js +35 -4
  25. package/dist/chant-version.d.ts +9 -0
  26. package/dist/chant-version.d.ts.map +1 -0
  27. package/dist/chant-version.js +16 -0
  28. package/dist/cli/commands/build.d.ts +8 -0
  29. package/dist/cli/commands/build.d.ts.map +1 -1
  30. package/dist/cli/commands/build.js +9 -8
  31. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  32. package/dist/cli/commands/check-lexicon-docs.js +30 -2
  33. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  34. package/dist/cli/commands/check-lexicon.js +13 -2
  35. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
  36. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  37. package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
  38. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  39. package/dist/cli/commands/init-lexicon.js +17 -8
  40. package/dist/cli/handlers/approve.d.ts.map +1 -1
  41. package/dist/cli/handlers/approve.js +19 -4
  42. package/dist/cli/handlers/build.d.ts.map +1 -1
  43. package/dist/cli/handlers/build.js +28 -0
  44. package/dist/cli/handlers/change-set.d.ts +10 -0
  45. package/dist/cli/handlers/change-set.d.ts.map +1 -1
  46. package/dist/cli/handlers/change-set.js +51 -1
  47. package/dist/cli/handlers/fan-out.d.ts +1 -1
  48. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  49. package/dist/cli/handlers/fan-out.js +14 -3
  50. package/dist/cli/handlers/pr.d.ts +8 -2
  51. package/dist/cli/handlers/pr.d.ts.map +1 -1
  52. package/dist/cli/handlers/pr.js +148 -24
  53. package/dist/cli/handlers/promote.d.ts.map +1 -1
  54. package/dist/cli/handlers/promote.js +256 -21
  55. package/dist/cli/handlers/release.d.ts +34 -0
  56. package/dist/cli/handlers/release.d.ts.map +1 -0
  57. package/dist/cli/handlers/release.js +203 -0
  58. package/dist/cli/handlers/run.d.ts +28 -2
  59. package/dist/cli/handlers/run.d.ts.map +1 -1
  60. package/dist/cli/handlers/run.js +170 -13
  61. package/dist/cli/main.d.ts.map +1 -1
  62. package/dist/cli/main.js +88 -13
  63. package/dist/cli/mcp/workspace-tools.js +1 -1
  64. package/dist/cli/parse-args.d.ts.map +1 -1
  65. package/dist/cli/parse-args.js +49 -3
  66. package/dist/cli/registry.d.ts +29 -3
  67. package/dist/cli/registry.d.ts.map +1 -1
  68. package/dist/cli/run-events.d.ts +29 -0
  69. package/dist/cli/run-events.d.ts.map +1 -0
  70. package/dist/cli/run-events.js +47 -0
  71. package/dist/cli/version.d.ts +3 -5
  72. package/dist/cli/version.d.ts.map +1 -1
  73. package/dist/cli/version.js +3 -13
  74. package/dist/codegen/docs-in-site.d.ts +71 -0
  75. package/dist/codegen/docs-in-site.d.ts.map +1 -0
  76. package/dist/codegen/docs-in-site.js +177 -0
  77. package/dist/codegen/docs-types.d.ts +14 -1
  78. package/dist/codegen/docs-types.d.ts.map +1 -1
  79. package/dist/codegen/docs.d.ts +18 -1
  80. package/dist/codegen/docs.d.ts.map +1 -1
  81. package/dist/codegen/docs.js +40 -7
  82. package/dist/codegen/rehype-base-url.mjs +15 -9
  83. package/dist/components/auto-release.d.ts +4 -1
  84. package/dist/components/auto-release.d.ts.map +1 -1
  85. package/dist/components/auto-release.js +2 -0
  86. package/dist/components/cli-support.d.ts +6 -0
  87. package/dist/components/cli-support.d.ts.map +1 -1
  88. package/dist/components/cli-support.js +10 -9
  89. package/dist/components/driver.d.ts +42 -9
  90. package/dist/components/driver.d.ts.map +1 -1
  91. package/dist/components/driver.js +81 -54
  92. package/dist/components/fan-out-run.d.ts +7 -0
  93. package/dist/components/fan-out-run.d.ts.map +1 -1
  94. package/dist/components/fan-out-run.js +35 -26
  95. package/dist/components/pr-run.d.ts +11 -0
  96. package/dist/components/pr-run.d.ts.map +1 -1
  97. package/dist/components/pr-run.js +20 -1
  98. package/dist/components/promote-order.d.ts +38 -0
  99. package/dist/components/promote-order.d.ts.map +1 -0
  100. package/dist/components/promote-order.js +62 -0
  101. package/dist/components/promote.d.ts +15 -2
  102. package/dist/components/promote.d.ts.map +1 -1
  103. package/dist/components/promote.js +17 -0
  104. package/dist/components/release-compile.d.ts +133 -0
  105. package/dist/components/release-compile.d.ts.map +1 -0
  106. package/dist/components/release-compile.js +376 -0
  107. package/dist/components/release-gate.d.ts +125 -0
  108. package/dist/components/release-gate.d.ts.map +1 -0
  109. package/dist/components/release-gate.js +282 -0
  110. package/dist/components/release-promote.d.ts +96 -0
  111. package/dist/components/release-promote.d.ts.map +1 -0
  112. package/dist/components/release-promote.js +204 -0
  113. package/dist/components/run-progress.d.ts +7 -0
  114. package/dist/components/run-progress.d.ts.map +1 -1
  115. package/dist/components/wave-plan.d.ts +3 -1
  116. package/dist/components/wave-plan.d.ts.map +1 -1
  117. package/dist/components/wave-plan.js +1 -1
  118. package/dist/config.d.ts +7 -0
  119. package/dist/config.d.ts.map +1 -1
  120. package/dist/config.js +1 -0
  121. package/dist/index.d.ts +1 -0
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +2 -0
  124. package/dist/lifecycle/change-set-store.d.ts +67 -0
  125. package/dist/lifecycle/change-set-store.d.ts.map +1 -0
  126. package/dist/lifecycle/change-set-store.js +96 -0
  127. package/dist/lifecycle/commit-sha.d.ts +13 -0
  128. package/dist/lifecycle/commit-sha.d.ts.map +1 -0
  129. package/dist/lifecycle/commit-sha.js +21 -0
  130. package/dist/lifecycle/gate-change-set.d.ts +69 -0
  131. package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
  132. package/dist/lifecycle/gate-change-set.js +106 -0
  133. package/dist/lifecycle/gate-ledger.d.ts +49 -1
  134. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  135. package/dist/lifecycle/gate-ledger.js +18 -0
  136. package/dist/lifecycle/release-cut.d.ts +107 -0
  137. package/dist/lifecycle/release-cut.d.ts.map +1 -0
  138. package/dist/lifecycle/release-cut.js +273 -0
  139. package/dist/lifecycle/release-ledger.d.ts +54 -0
  140. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  141. package/dist/lifecycle/release-ledger.js +53 -20
  142. package/dist/lifecycle/release-store.d.ts +190 -0
  143. package/dist/lifecycle/release-store.d.ts.map +1 -0
  144. package/dist/lifecycle/release-store.js +318 -0
  145. package/dist/lifecycle/run-ledger.d.ts +4 -0
  146. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  147. package/dist/lifecycle/run-ledger.js +2 -0
  148. package/dist/op/activities/change-coverage.d.ts.map +1 -1
  149. package/dist/op/activities/change-coverage.js +4 -2
  150. package/dist/op/activities/source-release.d.ts.map +1 -1
  151. package/dist/op/activities/source-release.js +7 -3
  152. package/dist/op/activities/source-rollback.d.ts.map +1 -1
  153. package/dist/op/activities/source-rollback.js +7 -3
  154. package/dist/op/activity-registry.d.ts +6 -3
  155. package/dist/op/activity-registry.d.ts.map +1 -1
  156. package/dist/op/activity-registry.js +7 -5
  157. package/dist/op/discover.d.ts.map +1 -1
  158. package/dist/op/discover.js +14 -7
  159. package/dist/op/gate-name.d.ts +11 -2
  160. package/dist/op/gate-name.d.ts.map +1 -1
  161. package/dist/op/gate-name.js +12 -3
  162. package/dist/op/gate-run-locator.d.ts +22 -0
  163. package/dist/op/gate-run-locator.d.ts.map +1 -1
  164. package/dist/op/gate-run-locator.js +31 -0
  165. package/dist/op/gate.d.ts +27 -3
  166. package/dist/op/gate.d.ts.map +1 -1
  167. package/dist/op/gate.js +57 -3
  168. package/dist/op/local-executor.d.ts +36 -1
  169. package/dist/op/local-executor.d.ts.map +1 -1
  170. package/dist/op/local-executor.js +158 -52
  171. package/dist/op/op-waves-run.d.ts.map +1 -1
  172. package/dist/op/op-waves-run.js +4 -0
  173. package/dist/op/run-live.d.ts +2 -0
  174. package/dist/op/run-live.d.ts.map +1 -1
  175. package/dist/op/run-live.js +1 -1
  176. package/dist/op/runtime.d.ts +46 -0
  177. package/dist/op/runtime.d.ts.map +1 -1
  178. package/dist/op/runtimes/local.d.ts.map +1 -1
  179. package/dist/op/runtimes/local.js +12 -2
  180. package/dist/point-decision.d.ts +2 -0
  181. package/dist/point-decision.d.ts.map +1 -1
  182. package/dist/point-decision.js +9 -2
  183. package/dist/run-event-store.d.ts +77 -0
  184. package/dist/run-event-store.d.ts.map +1 -0
  185. package/dist/run-event-store.js +204 -0
  186. package/dist/run-event-stream.d.ts +132 -0
  187. package/dist/run-event-stream.d.ts.map +1 -0
  188. package/dist/run-event-stream.js +238 -0
  189. package/dist/run-event.d.ts +267 -0
  190. package/dist/run-event.d.ts.map +1 -0
  191. package/dist/run-event.js +207 -0
  192. package/dist/wave-runtime.mjs +225 -242
  193. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  194. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  195. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  196. package/dist/workspace/change-set-show.d.ts +56 -0
  197. package/dist/workspace/change-set-show.d.ts.map +1 -0
  198. package/dist/workspace/change-set-show.js +54 -0
  199. package/dist/workspace/change-set-show.schema.json +102 -0
  200. package/dist/workspace/change-set.schema.json +28 -1
  201. package/dist/workspace/changes-cli.d.ts.map +1 -1
  202. package/dist/workspace/changes-cli.js +10 -1
  203. package/dist/workspace/changes.d.ts +37 -0
  204. package/dist/workspace/changes.d.ts.map +1 -1
  205. package/dist/workspace/changes.js +49 -2
  206. package/dist/workspace/changes.schema.json +135 -0
  207. package/dist/workspace/ci-green-cli.d.ts +19 -4
  208. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  209. package/dist/workspace/ci-green-cli.js +75 -12
  210. package/dist/workspace/ci-green-forge.d.ts +20 -3
  211. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  212. package/dist/workspace/ci-green-forge.js +6 -4
  213. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  214. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  215. package/dist/workspace/ci-green-workflow.js +6 -0
  216. package/dist/workspace/ci-green.d.ts +273 -13
  217. package/dist/workspace/ci-green.d.ts.map +1 -1
  218. package/dist/workspace/ci-green.js +449 -38
  219. package/dist/workspace/ci-last-green.schema.json +32 -2
  220. package/dist/workspace/ci-list.schema.json +525 -0
  221. package/dist/workspace/commit-links.d.ts +85 -0
  222. package/dist/workspace/commit-links.d.ts.map +1 -0
  223. package/dist/workspace/commit-links.js +163 -0
  224. package/dist/workspace/declaration.d.ts +19 -0
  225. package/dist/workspace/declaration.d.ts.map +1 -1
  226. package/dist/workspace/declaration.js +1 -1
  227. package/dist/workspace/declaration.schema.json +12 -0
  228. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  229. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  230. package/dist/workspace/identity-gate-rule.js +72 -7
  231. package/dist/workspace/identity.d.ts +12 -0
  232. package/dist/workspace/identity.d.ts.map +1 -1
  233. package/dist/workspace/identity.js +13 -1
  234. package/dist/workspace/intent.d.ts +6 -1
  235. package/dist/workspace/intent.d.ts.map +1 -1
  236. package/dist/workspace/intent.js +51 -1
  237. package/dist/workspace/ls.d.ts +4 -0
  238. package/dist/workspace/ls.d.ts.map +1 -1
  239. package/dist/workspace/ls.js +2 -0
  240. package/dist/workspace/ls.schema.json +3 -1
  241. package/dist/workspace/member-pipeline.d.ts +6 -0
  242. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  243. package/dist/workspace/member-pipeline.js +6 -0
  244. package/dist/workspace/promotion-order.d.ts +41 -0
  245. package/dist/workspace/promotion-order.d.ts.map +1 -0
  246. package/dist/workspace/promotion-order.js +95 -0
  247. package/dist/workspace/reason-codes.d.ts +2 -0
  248. package/dist/workspace/reason-codes.d.ts.map +1 -1
  249. package/dist/workspace/reason-codes.js +4 -1
  250. package/dist/workspace/releases.d.ts +72 -0
  251. package/dist/workspace/releases.d.ts.map +1 -0
  252. package/dist/workspace/releases.js +94 -0
  253. package/dist/workspace/releases.schema.json +391 -0
  254. package/dist/workspace/run-event.schema.json +288 -0
  255. package/dist/workspace/status-gates.d.ts +26 -1
  256. package/dist/workspace/status-gates.d.ts.map +1 -1
  257. package/dist/workspace/status-gates.js +31 -2
  258. package/dist/workspace/status-stewards.d.ts +4 -0
  259. package/dist/workspace/status-stewards.d.ts.map +1 -1
  260. package/dist/workspace/status-stewards.js +2 -0
  261. package/dist/workspace/status.d.ts +39 -0
  262. package/dist/workspace/status.d.ts.map +1 -1
  263. package/dist/workspace/status.js +48 -1
  264. package/dist/workspace/status.schema.json +70 -1
  265. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  266. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  267. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  268. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  269. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  270. package/dist/workspace/trust/op-run-statement.js +143 -0
  271. package/dist/workspace/trust/rotation.d.ts +18 -2
  272. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  273. package/dist/workspace/trust/rotation.js +65 -16
  274. package/dist/workspace/trust/seal.d.ts +11 -0
  275. package/dist/workspace/trust/seal.d.ts.map +1 -1
  276. package/dist/workspace/trust/seal.js +9 -0
  277. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  278. package/dist/workspace/trust/ssh-commit.js +10 -10
  279. package/package.json +1 -1
  280. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  281. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  282. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  283. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  284. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  285. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  286. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  287. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  288. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  289. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  290. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  291. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  292. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  293. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  294. package/src/__golden__/wave-loop/op-spend.json +3 -0
  295. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  296. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  297. package/src/change-set-graph.test.ts +72 -0
  298. package/src/change-set-graph.ts +133 -0
  299. package/src/change-set.ts +76 -6
  300. package/src/chant-version.ts +16 -0
  301. package/src/cli/commands/build.ts +19 -8
  302. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  303. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  304. package/src/cli/commands/check-lexicon.ts +14 -2
  305. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  306. package/src/cli/commands/init-lexicon.test.ts +25 -0
  307. package/src/cli/commands/init-lexicon.ts +18 -8
  308. package/src/cli/compiled-cli.e2e.test.ts +31 -6
  309. package/src/cli/handlers/approve-release.test.ts +80 -0
  310. package/src/cli/handlers/approve.ts +18 -2
  311. package/src/cli/handlers/build.test.ts +52 -0
  312. package/src/cli/handlers/build.ts +29 -0
  313. package/src/cli/handlers/change-set.ts +49 -1
  314. package/src/cli/handlers/fan-out.test.ts +48 -0
  315. package/src/cli/handlers/fan-out.ts +15 -3
  316. package/src/cli/handlers/operator.test.ts +37 -0
  317. package/src/cli/handlers/pr.test.ts +194 -0
  318. package/src/cli/handlers/pr.ts +168 -34
  319. package/src/cli/handlers/promote.test.ts +389 -5
  320. package/src/cli/handlers/promote.ts +279 -20
  321. package/src/cli/handlers/release-implicit.e2e.test.ts +153 -0
  322. package/src/cli/handlers/release.e2e.test.ts +85 -0
  323. package/src/cli/handlers/release.ts +206 -0
  324. package/src/cli/handlers/run-auto-release.test.ts +71 -0
  325. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  326. package/src/cli/handlers/run.test.ts +30 -0
  327. package/src/cli/handlers/run.ts +176 -15
  328. package/src/cli/main.test.ts +7 -0
  329. package/src/cli/main.ts +88 -13
  330. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  331. package/src/cli/mcp/workspace-tools.ts +1 -1
  332. package/src/cli/parse-args.ts +37 -3
  333. package/src/cli/registry.ts +29 -3
  334. package/src/cli/run-events.ts +54 -0
  335. package/src/cli/version.ts +3 -13
  336. package/src/codegen/docs-in-site.test.ts +166 -0
  337. package/src/codegen/docs-in-site.ts +182 -0
  338. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  339. package/src/codegen/docs-types.ts +14 -1
  340. package/src/codegen/docs.ts +60 -7
  341. package/src/codegen/rehype-base-url.d.mts +3 -1
  342. package/src/codegen/rehype-base-url.mjs +15 -9
  343. package/src/codegen/rehype-base-url.test.ts +34 -0
  344. package/src/components/auto-release.test.ts +21 -0
  345. package/src/components/auto-release.ts +6 -1
  346. package/src/components/cli-support.test.ts +82 -0
  347. package/src/components/cli-support.ts +16 -9
  348. package/src/components/driver.ts +129 -56
  349. package/src/components/fan-out-run.ts +56 -35
  350. package/src/components/pr-run.ts +30 -1
  351. package/src/components/promote-order.test.ts +90 -0
  352. package/src/components/promote-order.ts +75 -0
  353. package/src/components/promote.test.ts +10 -1
  354. package/src/components/promote.ts +26 -1
  355. package/src/components/release-compile.test.ts +147 -0
  356. package/src/components/release-compile.ts +445 -0
  357. package/src/components/release-gate.test.ts +215 -0
  358. package/src/components/release-gate.ts +341 -0
  359. package/src/components/release-promote.test.ts +159 -0
  360. package/src/components/release-promote.ts +232 -0
  361. package/src/components/run-progress.ts +7 -0
  362. package/src/components/wave-plan.ts +1 -1
  363. package/src/config.ts +7 -1
  364. package/src/index.ts +2 -0
  365. package/src/lifecycle/change-set-store.test.ts +217 -0
  366. package/src/lifecycle/change-set-store.ts +112 -0
  367. package/src/lifecycle/commit-sha.ts +23 -0
  368. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  369. package/src/lifecycle/gate-change-set.ts +133 -0
  370. package/src/lifecycle/gate-ledger.test.ts +33 -0
  371. package/src/lifecycle/gate-ledger.ts +66 -1
  372. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  373. package/src/lifecycle/release-cut.test.ts +411 -0
  374. package/src/lifecycle/release-cut.ts +367 -0
  375. package/src/lifecycle/release-ledger.test.ts +14 -0
  376. package/src/lifecycle/release-ledger.ts +85 -17
  377. package/src/lifecycle/release-store.ts +423 -0
  378. package/src/lifecycle/run-ledger.test.ts +10 -0
  379. package/src/lifecycle/run-ledger.ts +6 -0
  380. package/src/op/activities/change-coverage.ts +4 -1
  381. package/src/op/activities/source-release.ts +8 -3
  382. package/src/op/activities/source-rollback.ts +8 -4
  383. package/src/op/activity-registry.ts +7 -5
  384. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  385. package/src/op/discover-root.test.ts +39 -0
  386. package/src/op/discover.ts +15 -8
  387. package/src/op/gate-name.ts +13 -3
  388. package/src/op/gate-release.test.ts +126 -0
  389. package/src/op/gate-run-locator.test.ts +110 -0
  390. package/src/op/gate-run-locator.ts +35 -0
  391. package/src/op/gate.ts +88 -6
  392. package/src/op/local-executor.ts +207 -52
  393. package/src/op/op-waves-run.ts +4 -0
  394. package/src/op/run-events.test.ts +166 -0
  395. package/src/op/run-live.ts +1 -1
  396. package/src/op/runtime.ts +39 -0
  397. package/src/op/runtimes/local.test.ts +16 -0
  398. package/src/op/runtimes/local.ts +13 -2
  399. package/src/point-decision.ts +12 -2
  400. package/src/run-event-store.test.ts +144 -0
  401. package/src/run-event-store.ts +213 -0
  402. package/src/run-event-stream.test.ts +175 -0
  403. package/src/run-event-stream.ts +306 -0
  404. package/src/run-event.test.ts +277 -0
  405. package/src/run-event.ts +451 -0
  406. package/src/wave-loop-golden.test.ts +9 -0
  407. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  408. package/src/workspace/ancestry-index.test.ts +93 -0
  409. package/src/workspace/change-set-show.schema.json +102 -0
  410. package/src/workspace/change-set-show.ts +72 -0
  411. package/src/workspace/change-set.schema.json +28 -1
  412. package/src/workspace/changes-cli.ts +10 -1
  413. package/src/workspace/changes.schema.json +135 -0
  414. package/src/workspace/changes.test.ts +1 -1
  415. package/src/workspace/changes.ts +84 -2
  416. package/src/workspace/ci-green-cli.ts +75 -13
  417. package/src/workspace/ci-green-forge.ts +24 -4
  418. package/src/workspace/ci-green-runs.test.ts +242 -0
  419. package/src/workspace/ci-green-signed.test.ts +302 -0
  420. package/src/workspace/ci-green-workflow.test.ts +16 -0
  421. package/src/workspace/ci-green-workflow.ts +12 -0
  422. package/src/workspace/ci-green.test.ts +131 -2
  423. package/src/workspace/ci-green.ts +624 -42
  424. package/src/workspace/ci-last-green.schema.json +32 -2
  425. package/src/workspace/ci-list.schema.json +525 -0
  426. package/src/workspace/commit-links.test.ts +279 -0
  427. package/src/workspace/commit-links.ts +183 -0
  428. package/src/workspace/declaration.schema.json +12 -0
  429. package/src/workspace/declaration.ts +22 -2
  430. package/src/workspace/identity-gate-rule.ts +91 -7
  431. package/src/workspace/identity.ts +14 -1
  432. package/src/workspace/intent.ts +48 -1
  433. package/src/workspace/ls.schema.json +3 -1
  434. package/src/workspace/ls.ts +6 -0
  435. package/src/workspace/member-pipeline.test.ts +17 -0
  436. package/src/workspace/member-pipeline.ts +9 -0
  437. package/src/workspace/promotion-order.ts +97 -0
  438. package/src/workspace/read-contract.e2e.test.ts +4 -1
  439. package/src/workspace/reason-codes.test.ts +2 -1
  440. package/src/workspace/reason-codes.ts +4 -1
  441. package/src/workspace/releases.schema.json +391 -0
  442. package/src/workspace/releases.test.ts +108 -0
  443. package/src/workspace/releases.ts +122 -0
  444. package/src/workspace/run-event.schema.json +288 -0
  445. package/src/workspace/status-contract.test.ts +52 -1
  446. package/src/workspace/status-gates.ts +56 -1
  447. package/src/workspace/status-stewards.ts +6 -0
  448. package/src/workspace/status.schema.json +70 -1
  449. package/src/workspace/status.ts +80 -1
  450. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  451. package/src/workspace/trust/op-run-statement.ts +148 -0
  452. package/src/workspace/trust/rotation.ts +61 -14
  453. package/src/workspace/trust/seal.ts +10 -0
  454. package/src/workspace/trust/ssh-commit.ts +10 -9
  455. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  456. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  457. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  458. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  459. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -0,0 +1,451 @@
1
+ /**
2
+ * The run-event contract (#4019, behold #678): one stream of events for Op
3
+ * runs, component runs and pipeline runs, so anything that shows a run reads
4
+ * the same fields wherever the run happened.
5
+ *
6
+ * `workspace/run-event.schema.json` is the contract. These types
7
+ * mirror it, and {@link createRunEventWriter} and {@link writeRunEvent} are
8
+ * the one writer: every emitter hands a writer the type-specific body and
9
+ * the writer stamps the run's identity, the sequence number, the time and the
10
+ * contract fields on it, then writes one JSON line. The emitters themselves
11
+ * are in `./run-event-stream.ts` and the runtimes (#4041); the files
12
+ * they are kept in, replay and `--follow` are `./run-event-store.ts` (#4042).
13
+ *
14
+ * Two older streams predate this one and keep their shape behind
15
+ * `--progress-json`, while `--events-json` prints this one (#4041):
16
+ * `RunProgressEvent` (`./components/run-progress.ts`, from `chant run
17
+ * --components --progress-json`) and the Op path's `StepRecord` lines
18
+ * (`./op/local-executor.ts`, from `chant run <op> --progress-json`).
19
+ * {@link runEventOfProgress} and {@link runEventsOfStepRecord} say how each
20
+ * maps onto this contract.
21
+ *
22
+ * This module imports no runtime code beyond the chant version (`./chant-version.ts`), so a consumer
23
+ * can import the types and the writer without loading the rest of chant. It
24
+ * lives outside `workspace/` so that a level-0 import of `@intentius/chant`
25
+ * loads no workspace module (#2526).
26
+ */
27
+
28
+ import type { RunProgressEvent } from "./components/run-progress";
29
+ import type { RunOrigin } from "./lifecycle/release-ledger";
30
+ import type { StepRecord } from "./op/local-executor";
31
+ import { CHANT_VERSION } from "./chant-version";
32
+
33
+ /** The schema's `$id`, which every event carries as `$schema`. */
34
+ export const RUN_EVENT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/run-event/v1/run-event.schema.json";
35
+
36
+ /** The contract version every event carries as `contract`. */
37
+ export const RUN_EVENT_CONTRACT_VERSION = 1;
38
+
39
+ /** The first chant that writes contract version 1 of the run-event stream. */
40
+ export const RUN_EVENT_FLOOR = "0.124.28";
41
+
42
+ /** What ran: an Op, a component selection (`chant run --components`), or a CI pipeline. */
43
+ export type RunEventKind = "op" | "component" | "pipeline";
44
+
45
+ /** Where the run happened: in the process that started it, on a steward, or in a forge's CI. */
46
+ export type RunEventRuntime = "local" | "steward" | "ci";
47
+
48
+ /** How a run, wave, component or phase settled. `gated` and `waiting` wait on a person and are not failures. */
49
+ export type RunEventSettleStatus = "ok" | "failed" | "gated" | "waiting";
50
+
51
+ /**
52
+ * How a step settled. `skipped` says why in {@link RunEventStepSettle.skip};
53
+ * `cached` is for a cache hit standing in for the step (#4015).
54
+ */
55
+ export type RunEventStepStatus = "ok" | "failed" | "skipped" | "cached" | "gated" | "waiting";
56
+
57
+ /** Why a step was skipped. */
58
+ export type RunEventSkipReason = "receipt" | "resumed" | "refused" | "not-run";
59
+
60
+ /** How a gate's approval passed, when it was more than one person's approval of that gate. */
61
+ export type RunEventGateVia = "quorum" | "policy" | "point" | "alias" | "empty";
62
+
63
+ /** The run every event of a stream names: the same on every line. */
64
+ export interface RunEventRun {
65
+ /**
66
+ * The run's own id: an Op run's id, a component run's id (the id its
67
+ * release records carry), or the CI run id for a pipeline run.
68
+ */
69
+ runId: string;
70
+ kind: RunEventKind;
71
+ /** The Op's name, the component selection as given, or the pipeline's name. */
72
+ name: string;
73
+ /** The commit the run built from, or null when it did not start in a git checkout. */
74
+ gitSha: string | null;
75
+ runtime: RunEventRuntime;
76
+ /**
77
+ * Where the run ran and how to reach it, the shape a release record's
78
+ * `runOrigin` has. For an Op run in CI it locates the CI run, as the run
79
+ * record does (#4020), while `runId` stays the Op run's own id.
80
+ */
81
+ runOrigin: RunOrigin;
82
+ }
83
+
84
+ /** What every event carries besides its body. */
85
+ export interface RunEventEnvelope extends RunEventRun {
86
+ $schema: typeof RUN_EVENT_SCHEMA_ID;
87
+ contract: typeof RUN_EVENT_CONTRACT_VERSION;
88
+ /** The version of the chant that wrote the event. */
89
+ chant: string;
90
+ /** The event's place in its run's stream, from 0 at `run-start`. */
91
+ seq: number;
92
+ /** When it happened, ISO-8601. */
93
+ at: string;
94
+ }
95
+
96
+ export interface RunEventRunStart {
97
+ type: "run-start";
98
+ /** In a component run, the waves the run will attempt, in order. Null in an Op or pipeline run. */
99
+ waves: string[][] | null;
100
+ }
101
+
102
+ /** The last event of a stream. Its `status` is the run's verdict. */
103
+ export interface RunEventRunSettle {
104
+ type: "run-settle";
105
+ status: RunEventSettleStatus;
106
+ durationMs: number | null;
107
+ error?: string;
108
+ }
109
+
110
+ export interface RunEventWaveStart {
111
+ type: "wave-start";
112
+ /** 1-based, into `run-start`'s `waves`. */
113
+ wave: number;
114
+ components: string[];
115
+ }
116
+
117
+ export interface RunEventWaveSettle {
118
+ type: "wave-settle";
119
+ wave: number;
120
+ status: RunEventSettleStatus;
121
+ }
122
+
123
+ export interface RunEventComponentStart {
124
+ type: "component-start";
125
+ wave: number;
126
+ component: string;
127
+ }
128
+
129
+ export interface RunEventComponentSettle {
130
+ type: "component-settle";
131
+ wave: number;
132
+ component: string;
133
+ status: RunEventSettleStatus;
134
+ }
135
+
136
+ /** An Op's phase, a component composition's phase, or a pipeline's stage. `component` is null outside a component run. */
137
+ export interface RunEventPhaseStart {
138
+ type: "phase-start";
139
+ component: string | null;
140
+ phase: string;
141
+ }
142
+
143
+ export interface RunEventPhaseSettle {
144
+ type: "phase-settle";
145
+ component: string | null;
146
+ phase: string;
147
+ status: RunEventSettleStatus;
148
+ }
149
+
150
+ /** An Op or composition step, or a pipeline's job. */
151
+ export interface RunEventStepStart {
152
+ type: "step-start";
153
+ component: string | null;
154
+ phase: string;
155
+ /** The activity, `gate:<name>`, `effect:<name>`, or the job's name. */
156
+ step: string;
157
+ /**
158
+ * The step's place in its phase as authored, from 0, so two steps of one
159
+ * phase that share a name stay apart. Optional: added within contract 1 by
160
+ * chant 0.124.28 (#4041), and left out where the writer has no authored
161
+ * position, such as a step the executor adds itself.
162
+ */
163
+ index?: number;
164
+ }
165
+
166
+ export interface RunEventStepSettle {
167
+ type: "step-settle";
168
+ component: string | null;
169
+ phase: string;
170
+ step: string;
171
+ /** As on {@link RunEventStepStart.index}. */
172
+ index?: number;
173
+ status: RunEventStepStatus;
174
+ /** With `skipped`: why, or null when the writer does not know. */
175
+ skip?: RunEventSkipReason | null;
176
+ durationMs: number | null;
177
+ /** With `failed`: what failed. */
178
+ error?: string;
179
+ /** Why the step skipped, gated or waited, for a person. */
180
+ message?: string;
181
+ }
182
+
183
+ /** The run stopped at a gate nobody has approved, or a decision point nobody has answered. */
184
+ export interface RunEventGateWaiting {
185
+ type: "gate-waiting";
186
+ component: string | null;
187
+ phase: string;
188
+ step: string;
189
+ /** As on {@link RunEventStepStart.index}. */
190
+ index?: number;
191
+ /** The op the gate is recorded under, which `chant approve` takes. */
192
+ op: string;
193
+ /** The gate's name, or null when the step waits on a decision point alone. */
194
+ gate: string | null;
195
+ /** The open question's answer record id, or null. */
196
+ point: string | null;
197
+ message?: string;
198
+ }
199
+
200
+ export interface RunEventGateResolved {
201
+ type: "gate-resolved";
202
+ component: string | null;
203
+ phase: string;
204
+ step: string;
205
+ /** As on {@link RunEventStepStart.index}. */
206
+ index?: number;
207
+ op: string;
208
+ gate: string;
209
+ resolvedBy: string;
210
+ resolvedAt: string;
211
+ via: RunEventGateVia | null;
212
+ url?: string;
213
+ }
214
+
215
+ /** The type-specific part of an event: what an emitter hands the writer. */
216
+ export type RunEventBody =
217
+ | RunEventRunStart
218
+ | RunEventRunSettle
219
+ | RunEventWaveStart
220
+ | RunEventWaveSettle
221
+ | RunEventComponentStart
222
+ | RunEventComponentSettle
223
+ | RunEventPhaseStart
224
+ | RunEventPhaseSettle
225
+ | RunEventStepStart
226
+ | RunEventStepSettle
227
+ | RunEventGateWaiting
228
+ | RunEventGateResolved;
229
+
230
+ export type RunEventType = RunEventBody["type"];
231
+
232
+ /** Every event type, in the order a reader meets them. Closed: part of the contract. */
233
+ export const RUN_EVENT_TYPES = [
234
+ "run-start",
235
+ "wave-start",
236
+ "component-start",
237
+ "phase-start",
238
+ "step-start",
239
+ "gate-waiting",
240
+ "gate-resolved",
241
+ "step-settle",
242
+ "phase-settle",
243
+ "component-settle",
244
+ "wave-settle",
245
+ "run-settle",
246
+ ] as const satisfies readonly RunEventType[];
247
+
248
+ /** One line of a run's stream. */
249
+ export type RunEvent = RunEventBody extends infer B ? (B extends RunEventBody ? RunEventEnvelope & B : never) : never;
250
+
251
+ /** One event as its line: JSON with the envelope first, then the body, and a newline. */
252
+ export function serializeRunEvent(event: RunEvent): string {
253
+ return JSON.stringify(event) + "\n";
254
+ }
255
+
256
+ /** What {@link writeRunEvent} and {@link createRunEventWriter} need besides the body. */
257
+ export interface RunEventStamp {
258
+ run: RunEventRun;
259
+ seq: number;
260
+ /** When it happened. Defaults to now. */
261
+ at?: Date | string;
262
+ /** Defaults to this chant's version. */
263
+ chant?: string;
264
+ }
265
+
266
+ /**
267
+ * Build one event from its body and the run it belongs to. The envelope's
268
+ * fields come first, so every line reads the same way. No body names an
269
+ * envelope field, so neither overrides the other.
270
+ */
271
+ export function buildRunEvent(body: RunEventBody, stamp: RunEventStamp): RunEvent {
272
+ const at = stamp.at === undefined ? new Date().toISOString() : typeof stamp.at === "string" ? stamp.at : stamp.at.toISOString();
273
+ const { runId, kind, name, gitSha, runtime, runOrigin } = stamp.run;
274
+ const envelope: RunEventEnvelope = {
275
+ $schema: RUN_EVENT_SCHEMA_ID,
276
+ contract: RUN_EVENT_CONTRACT_VERSION,
277
+ chant: stamp.chant ?? CHANT_VERSION,
278
+ seq: stamp.seq,
279
+ at,
280
+ runId,
281
+ kind,
282
+ name,
283
+ gitSha,
284
+ runtime,
285
+ runOrigin,
286
+ };
287
+ return { ...envelope, ...body } as RunEvent;
288
+ }
289
+
290
+ /** Build one event and hand its line to `write`. Returns the event written. */
291
+ export function writeRunEvent(body: RunEventBody, stamp: RunEventStamp, write: (line: string) => void): RunEvent {
292
+ const event = buildRunEvent(body, stamp);
293
+ write(serializeRunEvent(event));
294
+ return event;
295
+ }
296
+
297
+ /** A run's writer: it keeps the run and the sequence, so an emitter hands it bodies only. */
298
+ export interface RunEventWriter {
299
+ /** Write the next event of the run and return it. */
300
+ emit(body: RunEventBody): RunEvent;
301
+ /** The `seq` the next event gets. */
302
+ readonly next: number;
303
+ }
304
+
305
+ export interface RunEventWriterOptions {
306
+ run: RunEventRun;
307
+ /** Where each line goes. Defaults to standard output. */
308
+ write?: (line: string) => void;
309
+ /** The clock. Defaults to `new Date()`. */
310
+ now?: () => Date;
311
+ /** The `seq` of the first event, for a writer that carries on a stream. Defaults to 0. */
312
+ seq?: number;
313
+ /** Defaults to this chant's version. */
314
+ chant?: string;
315
+ }
316
+
317
+ /** The one writer of a run's stream (#4019). Each `emit` writes one line and moves the sequence on. */
318
+ export function createRunEventWriter(opts: RunEventWriterOptions): RunEventWriter {
319
+ const write = opts.write ?? ((line: string) => void process.stdout.write(line));
320
+ const now = opts.now ?? (() => new Date());
321
+ let seq = opts.seq ?? 0;
322
+ return {
323
+ emit(body) {
324
+ return writeRunEvent(body, { run: opts.run, seq: seq++, at: now(), chant: opts.chant }, write);
325
+ },
326
+ get next() {
327
+ return seq;
328
+ },
329
+ };
330
+ }
331
+
332
+ // ── The older streams, in terms of this one ─────────────────────────────────
333
+
334
+ /**
335
+ * A `RunProgressEvent` (`chant run --components --progress-json`) as a
336
+ * run-event body. The older stream has no step timings, so `durationMs` is
337
+ * null on the settles it maps to.
338
+ */
339
+ export function runEventOfProgress(event: RunProgressEvent): RunEventBody {
340
+ switch (event.type) {
341
+ case "run-start":
342
+ return { type: "run-start", waves: event.waves };
343
+ case "wave-start":
344
+ return { type: "wave-start", wave: event.wave, components: event.components };
345
+ case "component-start":
346
+ return { type: "component-start", wave: event.wave, component: event.component };
347
+ case "phase-start":
348
+ return { type: "phase-start", component: event.component, phase: event.phase };
349
+ case "step":
350
+ if (event.status === "running") return { type: "step-start", component: event.component, phase: event.phase, step: event.step };
351
+ return {
352
+ type: "step-settle",
353
+ component: event.component,
354
+ phase: event.phase,
355
+ step: event.step,
356
+ status: event.status,
357
+ durationMs: null,
358
+ ...(event.error !== undefined ? { error: event.error } : {}),
359
+ };
360
+ case "phase-done":
361
+ return { type: "phase-settle", component: event.component, phase: event.phase, status: event.status };
362
+ case "component-done":
363
+ return { type: "component-settle", wave: event.wave, component: event.component, status: event.status };
364
+ case "wave-done":
365
+ return { type: "wave-settle", wave: event.wave, status: event.status };
366
+ case "run-done":
367
+ return { type: "run-settle", status: event.status, durationMs: null };
368
+ }
369
+ }
370
+
371
+ const GATE_PREFIX = "gate:";
372
+
373
+ /**
374
+ * A settled `StepRecord` (`chant run <op> --progress-json`) as run-event
375
+ * bodies: a gate's `gate-resolved` or `gate-waiting` when the record carries
376
+ * one, then the `step-settle`. `op` is the run's Op, which a gate is recorded
377
+ * under unless the record names another.
378
+ *
379
+ * A record skipped with nothing to say why maps to `skip: null`. The local
380
+ * executor writes the same record for a gate nobody has approved, for a step
381
+ * of an effect whose receipt matched and for a step an earlier failure
382
+ * stopped, so only an emitter that knows which can say `gated`, `receipt` or
383
+ * `not-run`. The run's own status still says `gated`. The local executor
384
+ * (#4041) knows, and passes `skip` for a bare skipped record and `gate` for
385
+ * a gate step nobody has approved; it passes `index`, the step's place in
386
+ * its phase, too.
387
+ */
388
+ export function runEventsOfStepRecord(
389
+ record: StepRecord,
390
+ ctx: {
391
+ op: string;
392
+ component?: string | null;
393
+ skip?: RunEventSkipReason;
394
+ index?: number;
395
+ /** The gate a skipped gate step stopped at, when the emitter knows it. */
396
+ gate?: { op: string; gate: string };
397
+ },
398
+ ): RunEventBody[] {
399
+ const component = ctx.component ?? null;
400
+ const at = { component, phase: record.phase, step: record.fn, ...(ctx.index !== undefined ? { index: ctx.index } : {}) };
401
+ const out: RunEventBody[] = [];
402
+ const settle = (status: RunEventStepStatus, extra: Partial<RunEventStepSettle> = {}): RunEventStepSettle => ({
403
+ type: "step-settle",
404
+ ...at,
405
+ status,
406
+ durationMs: record.durationMs,
407
+ ...extra,
408
+ });
409
+ const gateOfFn = record.fn.startsWith(GATE_PREFIX) ? record.fn.slice(GATE_PREFIX.length) : null;
410
+
411
+ if (record.approval) {
412
+ const a = record.approval;
413
+ out.push({
414
+ type: "gate-resolved",
415
+ ...at,
416
+ op: ctx.op,
417
+ gate: a.gate,
418
+ resolvedBy: a.resolvedBy,
419
+ resolvedAt: a.timestamp,
420
+ via: a.via ?? null,
421
+ ...(a.url !== undefined ? { url: a.url } : {}),
422
+ });
423
+ }
424
+ if (record.status === "ok") {
425
+ out.push(settle("ok"));
426
+ return out;
427
+ }
428
+ if (record.status === "fail") {
429
+ out.push(settle("failed", record.error !== undefined ? { error: record.error } : {}));
430
+ return out;
431
+ }
432
+ const message = record.refusal !== undefined ? { message: record.refusal } : {};
433
+ const stoppedAt = record.gate ?? ctx.gate;
434
+ if (record.point) {
435
+ out.push({ type: "gate-waiting", ...at, op: ctx.op, gate: gateOfFn, point: record.point.id, ...message });
436
+ out.push(settle("waiting", message));
437
+ } else if (stoppedAt) {
438
+ out.push({ type: "gate-waiting", ...at, op: stoppedAt.op, gate: stoppedAt.gate, point: null, ...message });
439
+ out.push(settle("gated", message));
440
+ } else if (gateOfFn !== null && record.refusal !== undefined) {
441
+ out.push({ type: "gate-waiting", ...at, op: ctx.op, gate: gateOfFn, point: null, ...message });
442
+ out.push(settle("gated", message));
443
+ } else if (record.resumed) {
444
+ out.push(settle("skipped", { skip: "resumed", message: `resumed at gate ${record.resumed.gate} from run ${record.resumed.run}` }));
445
+ } else if (record.refusal !== undefined) {
446
+ out.push(settle("skipped", { skip: "refused", ...message }));
447
+ } else {
448
+ out.push(settle("skipped", { skip: ctx.skip ?? null }));
449
+ }
450
+ return out;
451
+ }
@@ -34,6 +34,9 @@ import type { DriverComponent } from "./components/driver";
34
34
  import type { RunProgressEvent } from "./components/run-progress";
35
35
  import type { WaveRecord } from "./gated-waves";
36
36
 
37
+ // #4020: every pending fact names the commit it was recorded at. The transcripts pin one.
38
+ vi.mock("./lifecycle/commit-sha", () => ({ resolveCommitSha: async () => "1".repeat(40) }));
39
+
37
40
  const digest = (n: string) => `sha256:${n.repeat(64)}`;
38
41
  const NOW = "2026-10-11T00:00:00.000Z";
39
42
  const BEFORE = "2026-10-10T00:00:00Z";
@@ -46,6 +49,12 @@ beforeEach(() => {
46
49
  // A pull request's address would land on every pending fact too.
47
50
  vi.stubEnv("GITHUB_REF_NAME", "");
48
51
  vi.stubEnv("CI_MERGE_REQUEST_IID", "");
52
+ // A CI run's id would land on them as well (#4020).
53
+ vi.stubEnv("GITHUB_RUN_ID", "");
54
+ vi.stubEnv("CI_PIPELINE_ID", "");
55
+ // And the commit the CI job started for, which a decision point's gate reads from the environment.
56
+ vi.stubEnv("GITHUB_SHA", "");
57
+ vi.stubEnv("CI_COMMIT_SHA", "");
49
58
  });
50
59
  afterEach(() => vi.unstubAllEnvs());
51
60
 
@@ -0,0 +1,58 @@
1
+ /**
2
+ * What every emitter's test checks about a run's stream (#4041): each line
3
+ * passes `run-event.schema.json`, the stream opens with `run-start` and ends
4
+ * with one `run-settle`, `seq` counts from 0, every line names the same run,
5
+ * and every step that starts settles after its start, under the same step id.
6
+ */
7
+
8
+ import { expect } from "vitest";
9
+ import { contract } from "./contract-repo";
10
+ import schema from "../run-event.schema.json";
11
+ import type { RunEvent } from "../../run-event";
12
+
13
+ const { expectValid } = contract(schema);
14
+
15
+ /** A step's id in the stream: its component, phase, name and place. */
16
+ export function stepKey(e: { component: string | null; phase: string; step: string; index?: number }): string {
17
+ return JSON.stringify([e.component, e.phase, e.step, e.index ?? null]);
18
+ }
19
+
20
+ /** Check a stream's lines and hand back its events. */
21
+ export function checkRunEventLines(lines: string[]): RunEvent[] {
22
+ for (const line of lines) expect(line.endsWith("\n")).toBe(true);
23
+ const events = lines.map((l) => JSON.parse(l) as RunEvent);
24
+ for (const event of events) expectValid(event);
25
+ expect(events.map((e) => e.seq)).toEqual(events.map((_, i) => i));
26
+ expect(events[0]?.type).toBe("run-start");
27
+ expect(events.at(-1)?.type).toBe("run-settle");
28
+ expect(events.filter((e) => e.type === "run-settle")).toHaveLength(1);
29
+ const runs = new Set(events.map((e) => JSON.stringify([e.runId, e.kind, e.name, e.gitSha, e.runtime, e.runOrigin])));
30
+ expect(runs.size).toBe(1);
31
+
32
+ const open = new Map<string, number>();
33
+ for (const e of events) {
34
+ if (e.type === "step-start") {
35
+ const key = stepKey(e);
36
+ expect(open.has(key), `${key} started twice`).toBe(false);
37
+ open.set(key, e.seq);
38
+ } else if (e.type === "step-settle") {
39
+ const key = stepKey(e);
40
+ if (open.has(key)) open.delete(key);
41
+ // Only a step that never ran settles without a start.
42
+ else expect(e.status, `${key} settled ${e.status} with no step-start`).toBe("skipped");
43
+ }
44
+ }
45
+ expect([...open.keys()], "steps that started and never settled").toEqual([]);
46
+ return events;
47
+ }
48
+
49
+ /** The step events of a stream, as `[type, step, status]`, for a compact assertion. */
50
+ export function stepTrail(events: RunEvent[]): Array<[string, string, string?]> {
51
+ const out: Array<[string, string, string?]> = [];
52
+ for (const e of events) {
53
+ if (e.type === "step-start") out.push([e.type, e.step]);
54
+ else if (e.type === "step-settle") out.push([e.type, e.step, e.skip ? `${e.status}:${e.skip}` : e.status]);
55
+ else if (e.type === "gate-waiting" || e.type === "gate-resolved") out.push([e.type, e.step]);
56
+ }
57
+ return out;
58
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * #4032: the signer-position lookup and the decision windows of the intent
3
+ * walk read the commit graph once, in process, where they used to start a
4
+ * git process per question. Each answer must stay what git gives.
5
+ *
6
+ * The repository has merges of branches that forked at different points, a
7
+ * branch merged twice, and a branch never merged, so a commit's way into the
8
+ * first-parent line is not its date order.
9
+ */
10
+
11
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
12
+ import { descendants } from "./intent";
13
+ import { SignerPositions, type SignerVersion } from "./trust/rotation";
14
+ import { TestRepo } from "./trust/test-repo";
15
+
16
+ let r: TestRepo;
17
+ let all: string[];
18
+ let unmerged: string;
19
+
20
+ function version(n: number, commit: string): SignerVersion {
21
+ return { version: n, commit, digest: `d${n}`, threshold: 1, signers: [], excluded: [], signedBy: [] };
22
+ }
23
+
24
+ beforeAll(() => {
25
+ r = new TestRepo("4032-ancestry");
26
+ r.commit("m1");
27
+ r.git(["branch", "a"]);
28
+ r.commit("m2");
29
+ r.git(["checkout", "-q", "a"]);
30
+ r.commit("a1");
31
+ r.git(["branch", "b"]);
32
+ r.commit("a2");
33
+ r.git(["checkout", "-q", "b"]);
34
+ r.commit("b1");
35
+ r.commit("b2");
36
+ r.git(["checkout", "-q", "main"]);
37
+ r.git(["merge", "-q", "--no-ff", "--no-gpg-sign", "-m", "merge a", "a"]);
38
+ r.commit("m3");
39
+ r.git(["checkout", "-q", "a"]);
40
+ r.commit("a3");
41
+ r.git(["checkout", "-q", "main"]);
42
+ r.git(["merge", "-q", "--no-ff", "--no-gpg-sign", "-m", "merge b", "b"]);
43
+ r.git(["merge", "-q", "--no-ff", "--no-gpg-sign", "-m", "merge a again", "a"]);
44
+ r.commit("m4");
45
+ r.git(["checkout", "-q", "-b", "side", "HEAD~2"]);
46
+ unmerged = r.commit("never merged");
47
+ r.git(["checkout", "-q", "main"]);
48
+ all = r.git(["rev-list", "--all"]).split("\n").filter(Boolean);
49
+ });
50
+
51
+ afterAll(() => r.cleanup());
52
+
53
+ describe("SignerPositions (#4032)", () => {
54
+ test("a full commit id is placed where git merge-base places it, for every commit", () => {
55
+ const base = r.head();
56
+ const line = r.git(["rev-list", "--first-parent", base]).split("\n").filter(Boolean);
57
+ // Versions made at the oldest, a middle and the newest first-parent commits.
58
+ const history = { versions: [version(1, line.at(-1)!), version(2, line[Math.floor(line.length / 2)]), version(3, line[0])] };
59
+ const fast = new SignerPositions(r.dir, base, history);
60
+ const slow = new SignerPositions(r.dir, base, history);
61
+ expect(all).toContain(unmerged);
62
+ for (const c of all) {
63
+ // An abbreviated id takes the old path, one git merge-base per step of a binary search.
64
+ expect(fast.versionFor(c)?.version ?? null, c).toBe(slow.versionFor(c.slice(0, 12))?.version ?? null);
65
+ }
66
+ expect(fast.versionFor(unmerged)?.version).toBe(3);
67
+ });
68
+
69
+ test("an id the repository doesn't have is judged by the latest version, as before", () => {
70
+ const base = r.head();
71
+ const p = new SignerPositions(r.dir, base, { versions: [version(1, base)] });
72
+ expect(p.versionFor("0".repeat(40))?.version).toBe(1);
73
+ });
74
+ });
75
+
76
+ describe("descendants (#4032)", () => {
77
+ test("matches git rev-list --ancestry-path for every pair of commits", () => {
78
+ for (const rev of [r.head(), unmerged]) {
79
+ for (const c of all) {
80
+ const expected = new Set([c, ...r.git(["rev-list", "--ancestry-path", `${c}..${rev}`]).split("\n").filter(Boolean)]);
81
+ expect(descendants(r.dir, c, rev), `${c}..${rev}`).toEqual(expected);
82
+ }
83
+ }
84
+ });
85
+
86
+ test("an abbreviated id, or one the repository doesn't have, still answers", () => {
87
+ const short = all.at(-1)!.slice(0, 12);
88
+ const listed = r.git(["rev-list", "--ancestry-path", `${short}..${r.head()}`]).split("\n").filter(Boolean);
89
+ expect(listed.length).toBeGreaterThan(0);
90
+ expect(descendants(r.dir, short, r.head())).toEqual(new Set([short, ...listed]));
91
+ expect(descendants(r.dir, "0".repeat(40), r.head())).toEqual(new Set(["0".repeat(40)]));
92
+ });
93
+ });