@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,102 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://intentius.io/chant/schemas/workspace/change-set-show/v1/change-set-show.schema.json",
4
+ "title": "chant change-set show output",
5
+ "description": "What `chant change-set show <digest> --json` prints (#4022): the change-set document a gate bound, stored on chant/lifecycle under its digest (_change-sets/<digest>.json, under _members/<member>/ for a workspace member). A pull-request gate's apply stores it, and chant components promote --release stores a release gate's (#4045), which names the release and environment it was planned for. Each entry carries the graph node it changes. It reads the local chant/lifecycle branch and never fetches. Version 1 of the read contract. Readers ignore fields they do not know; a field is only ever added within a version. The codes are a closed list, each in the one closed list of `reason-codes.ts`. Contract version 1 of this document is written by chant 0.124.28 and newer; the read contract as a whole by chant 0.81.0 and newer.",
6
+ "oneOf": [{ "$ref": "#/$defs/result" }, { "$ref": "#/$defs/failure" }],
7
+ "$defs": {
8
+ "result": {
9
+ "type": "object",
10
+ "description": "The ledger was read. Exit code 0 when a change set is shown, 1 when changeSet is null.",
11
+ "required": ["$schema", "contract", "chant", "member", "digest", "changeSet", "reason"],
12
+ "properties": {
13
+ "$schema": { "const": "https://intentius.io/chant/schemas/workspace/change-set-show/v1/change-set-show.schema.json" },
14
+ "contract": { "const": 1 },
15
+ "chant": { "type": "string" },
16
+ "error": false,
17
+ "member": {
18
+ "type": ["string", "null"],
19
+ "description": "The member whose ledger was read, its path of names joined with / when nested, or null for a project outside a workspace."
20
+ },
21
+ "digest": {
22
+ "type": "string",
23
+ "pattern": "^(?:jcs1-)?sha256:[0-9a-f]{64}$",
24
+ "description": "The digest asked for: a gate's planDigest."
25
+ },
26
+ "changeSet": {
27
+ "description": "The stored change-set document (change-set.schema.json), whose digest equals digest. Each entry has node, the graph node it changes as chant workspace graph names it (<member>/<id> in a workspace), or null when the graph holds no node for its address. Its graph says which member's graph that was and the commit it was read at, or is null when no graph could be read. Null when nothing is stored under digest, as for a component gate, which binds a lifecycle-plan digest, and when reason is ledger-malformed.",
28
+ "oneOf": [
29
+ { "type": "null" },
30
+ {
31
+ "type": "object",
32
+ "required": ["$schema", "contract", "digest", "members", "entries", "summary"],
33
+ "properties": {
34
+ "$schema": { "const": "https://intentius.io/chant/schemas/workspace/change-set/v1/change-set.schema.json" },
35
+ "contract": { "const": 1 },
36
+ "digest": { "type": "string" },
37
+ "releaseDigest": { "type": "string", "description": "Added in contract 1 by #4045, written by chant 0.124.28 and newer: the release a release gate's change set was planned for. Absent on a pull request's." },
38
+ "environment": { "type": "string", "description": "Added in contract 1 by #4045, written by chant 0.124.28 and newer: the environment a release gate's change set was planned for. Absent on a pull request's." },
39
+ "members": { "type": "array" },
40
+ "entries": {
41
+ "type": "array",
42
+ "items": {
43
+ "type": "object",
44
+ "required": ["member", "address", "action", "node"],
45
+ "properties": {
46
+ "node": { "type": ["string", "null"] }
47
+ }
48
+ }
49
+ },
50
+ "graph": {
51
+ "oneOf": [
52
+ { "type": "null" },
53
+ {
54
+ "type": "object",
55
+ "required": ["member", "at"],
56
+ "properties": {
57
+ "member": { "type": ["string", "null"] },
58
+ "at": { "type": ["string", "null"] }
59
+ }
60
+ }
61
+ ]
62
+ }
63
+ }
64
+ }
65
+ ]
66
+ },
67
+ "reason": {
68
+ "description": "Null when the file was read, or when none is stored. ledger-malformed when a file is stored under digest and it is not a change-set document whose content hashes to digest.",
69
+ "oneOf": [
70
+ { "type": "null" },
71
+ {
72
+ "type": "object",
73
+ "required": ["code", "message"],
74
+ "properties": {
75
+ "code": { "enum": ["ledger-malformed"] },
76
+ "message": { "type": "string" }
77
+ }
78
+ }
79
+ ]
80
+ }
81
+ }
82
+ },
83
+ "failure": {
84
+ "type": "object",
85
+ "description": "Nothing was read. Exit code 1.",
86
+ "required": ["$schema", "contract", "chant", "error"],
87
+ "properties": {
88
+ "$schema": { "const": "https://intentius.io/chant/schemas/workspace/change-set-show/v1/change-set-show.schema.json" },
89
+ "contract": { "const": 1 },
90
+ "chant": { "type": "string" },
91
+ "error": {
92
+ "type": "object",
93
+ "required": ["code", "message"],
94
+ "properties": {
95
+ "code": { "enum": ["not-a-git-repository", "ledger-unreadable"] },
96
+ "message": { "type": "string" }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The stored change-set read (#4022): `chant change-set show <digest> --json`.
3
+ *
4
+ * Prints the change-set document a gate bound, as `components pr-apply`
5
+ * stored it, or as a release gate stores it with #4045
6
+ * (`../lifecycle/change-set-store.ts`). Each entry carries the graph `node`
7
+ * it changes, as `chant workspace graph` names nodes. It reads the local
8
+ * `chant/lifecycle` branch and never fetches.
9
+ *
10
+ * A digest with nothing stored under it is a result with `changeSet: null`,
11
+ * since a component gate binds a lifecycle-plan digest and stores no
12
+ * document. A stored file that is not the document its digest names is also
13
+ * `changeSet: null`, with the reason `ledger-malformed`. A read that fails
14
+ * outright is the failure `ledger-unreadable`.
15
+ */
16
+
17
+ import { execFileSync } from "node:child_process";
18
+ import type { ChangeSetDocument } from "../change-set";
19
+ import { readStoredChangeSet } from "../lifecycle/change-set-store";
20
+ import { resolveMemberLedger } from "../lifecycle/member-ledger";
21
+ import { readerVersion } from "./declaration";
22
+
23
+ export const CHANGE_SET_SHOW_CONTRACT_VERSION = 1;
24
+ export const CHANGE_SET_SHOW_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/change-set-show/v1/change-set-show.schema.json";
25
+
26
+ /** Why the read stopped. Closed; each code is in `reason-codes.ts`. */
27
+ export const CHANGE_SET_SHOW_ERROR_CODES = ["not-a-git-repository", "ledger-unreadable"] as const;
28
+ /** Why a stored file was not shown. */
29
+ export const CHANGE_SET_SHOW_REASON_CODES = ["ledger-malformed"] as const;
30
+
31
+ /** What a change-set digest looks like: a plan digest's form. */
32
+ export const CHANGE_SET_DIGEST_PATTERN = /^(?:jcs1-)?sha256:[0-9a-f]{64}$/;
33
+
34
+ export type ChangeSetShowDocument =
35
+ | {
36
+ $schema: string;
37
+ contract: number;
38
+ chant: string;
39
+ /** The member whose ledger was read, its path of names joined with `/`, or null outside a workspace. */
40
+ member: string | null;
41
+ digest: string;
42
+ /** The stored document, or null when none is stored under the digest. */
43
+ changeSet: ChangeSetDocument | null;
44
+ reason: { code: (typeof CHANGE_SET_SHOW_REASON_CODES)[number]; message: string } | null;
45
+ }
46
+ | { $schema: string; contract: number; chant: string; error: { code: (typeof CHANGE_SET_SHOW_ERROR_CODES)[number]; message: string } };
47
+
48
+ /**
49
+ * Read the change set stored under `digest` in the ledger of the member at
50
+ * `cwd`, or of `member` (its path of names, outermost first) when given.
51
+ */
52
+ export async function changeSetShowDocument(req: { cwd: string; digest: string; member?: readonly string[] }): Promise<ChangeSetShowDocument> {
53
+ const head = { $schema: CHANGE_SET_SHOW_SCHEMA_ID, contract: CHANGE_SET_SHOW_CONTRACT_VERSION, chant: readerVersion() };
54
+ try {
55
+ execFileSync("git", ["rev-parse", "--git-dir"], { cwd: req.cwd, stdio: "ignore" });
56
+ } catch {
57
+ return { ...head, error: { code: "not-a-git-repository", message: "change sets are read from the chant/lifecycle branch, and this directory is not in a git repository" } };
58
+ }
59
+ try {
60
+ const members = req.member ? [...req.member] : (await resolveMemberLedger(req.cwd)).members;
61
+ const read = await readStoredChangeSet(req.digest, { cwd: req.cwd, member: members });
62
+ return {
63
+ ...head,
64
+ member: members.length > 0 ? members.join("/") : null,
65
+ digest: req.digest,
66
+ changeSet: read.status === "found" ? read.document : null,
67
+ reason: read.status === "malformed" ? { code: "ledger-malformed", message: read.message } : null,
68
+ };
69
+ } catch (err) {
70
+ return { ...head, error: { code: "ledger-unreadable", message: `reading the change set failed: ${err instanceof Error ? err.message : String(err)}` } };
71
+ }
72
+ }
@@ -26,7 +26,16 @@
26
26
  "digest": {
27
27
  "type": "string",
28
28
  "pattern": "^(?:jcs1-)?sha256:[0-9a-f]{64}$",
29
- "description": "computePlanDigest(\"change-set-document\", \u2026) over the set digest (computePlanDigest(\"change-set\", \u2026) over the members' { member, planDigest } pairs sorted by member), each member's member, status and holes, the entries and the side effects, provisional members and their entries left out. What a gate over the whole run binds."
29
+ "description": "computePlanDigest(\"change-set-document\", \u2026) over the set digest (computePlanDigest(\"change-set\", \u2026) over the members' { member, planDigest } pairs sorted by member), each member's member, status and holes, the entries without their node, the side effects, and on a release gate's document its releaseDigest and environment, provisional members and their entries left out. What a gate over the whole run binds."
30
+ },
31
+ "releaseDigest": {
32
+ "type": "string",
33
+ "pattern": "^(?:jcs1-)?sha256:[0-9a-f]{64}$",
34
+ "description": "Added in contract 1 by #4045, written by chant 0.124.28 and newer. The release a release gate's change set was planned for: the releaseDigest of the release chant components promote --release deploys. Set with environment, and absent on every other change set. Part of digest, so a stored change set names the one release its gate bound."
35
+ },
36
+ "environment": {
37
+ "type": "string",
38
+ "description": "Added in contract 1 by #4045, written by chant 0.124.28 and newer. The environment a release gate's change set was planned for, set with releaseDigest. Part of digest."
30
39
  },
31
40
  "members": {
32
41
  "type": "array",
@@ -59,6 +68,20 @@
59
68
  },
60
69
  "summary": {
61
70
  "$ref": "#/$defs/summary"
71
+ },
72
+ "graph": {
73
+ "description": "Added in contract 1 by #4022, written by chant 0.124.28 and newer on a stored change set. The graph the entries' node ids come from: the workspace member whose graph it is (null outside a workspace) and the commit it was read at (null when unknown). Null when no graph could be read, so every node is null. Absent on a document never mapped. Not part of digest.",
74
+ "oneOf": [
75
+ { "type": "null" },
76
+ {
77
+ "type": "object",
78
+ "required": ["member", "at"],
79
+ "properties": {
80
+ "member": { "type": ["string", "null"] },
81
+ "at": { "type": ["string", "null"] }
82
+ }
83
+ }
84
+ ]
62
85
  }
63
86
  },
64
87
  "$defs": {
@@ -244,6 +267,10 @@
244
267
  "items": {
245
268
  "$ref": "#/$defs/attribute"
246
269
  }
270
+ },
271
+ "node": {
272
+ "type": ["string", "null"],
273
+ "description": "Added in contract 1 by #4022, written by chant 0.124.28 and newer on a stored change set (chant change-set show). The graph node the entry changes, as chant graph --format ir names it, with the member in front (<member>/<id>) inside a workspace, as chant workspace graph names it. A chant entry's node is its address; a terraform-family entry's is <root>/<block key>, where the block key is each module call as module.<name>/ and then the block's address with no index. Null when the graph holds no node for the address, such as a delete of a block the source no longer declares. Absent on a document never mapped onto a graph. Not part of digest."
247
274
  }
248
275
  }
249
276
  },
@@ -32,13 +32,22 @@ export function formatChanges(doc: Result): string {
32
32
  out.push(`${p.status.padEnd(12)} ${p.change.padEnd(8)} ${p.path}${p.from ? ` (from ${p.from})` : ""}${why ? `; ${why}` : ""}`);
33
33
  }
34
34
  for (const f of doc.findings) out.push(`${f.severity === "fail" ? "error " : "warning"} ${f.code}: ${f.message}`);
35
+ if (doc.links) {
36
+ const l = doc.links;
37
+ const n = (state: string) => l.commits.filter((c) => c.state === state).length;
38
+ out.push(`links ${l.commits.length} commits: ${n("linked")} name a work item, ${n("unlinked")} do not, ${n("wrong")} name one that is not there; work kinds: ${l.kinds.join(", ")}`);
39
+ for (const f of l.findings) {
40
+ out.push(`${f.severity === "fail" ? "error " : "warning"} ${f.code}: ${f.message}`);
41
+ out.push(` fix: ${f.fix}`);
42
+ }
43
+ }
35
44
  if (doc.scope) {
36
45
  const sc = doc.scope;
37
46
  out.push(`scope ${sc.commits.length} commits judged against the declaration at ${sc.base.slice(0, 8)}; restricted: ${sc.restricted.join(", ") || "none"}`);
38
47
  for (const f of sc.findings) out.push(`error ${f.code}: ${f.message}`);
39
48
  }
40
49
  const s = doc.summary;
41
- out.push(`${s.paths} changed paths: ${s.covered} covered, ${s.uncovered} uncovered, ${s.outOfScope} out of scope, ${s.ignored} ignored, ${s.records} records; ${doc.findings.length} findings`);
50
+ out.push(`${s.paths} changed paths: ${s.covered} covered, ${s.uncovered} uncovered, ${s.outOfScope} out of scope, ${s.ignored} ignored, ${s.records} records; ${doc.findings.length} findings${doc.links ? `, ${doc.links.findings.length} commit link findings` : ""}`);
42
51
  return out.join("\n");
43
52
  }
44
53
 
@@ -210,6 +210,17 @@
210
210
  }
211
211
  ]
212
212
  },
213
+ "links": {
214
+ "description": "Whether each trunk commit of the range names the work item it belongs to (#4029): the first-parent commits of base..head, merges left out. A commit names one with a `Chant-Record: <kind>:<id>` trailer of a work kind the declaration names, or a `Chant-Lease` trailer a lease history on chant/lifecycle resolves to a work item. A window match does not count. Null when no work kind was read. The field is added within contract version 1, and is written by chant 0.124.28 and newer; a reader that finds none is reading an older chant. Its findings follow `severity`, like `findings`.",
215
+ "oneOf": [
216
+ {
217
+ "type": "null"
218
+ },
219
+ {
220
+ "$ref": "#/$defs/links"
221
+ }
222
+ ]
223
+ },
213
224
  "ok": {
214
225
  "type": "boolean",
215
226
  "description": "False when severity is fail and there is a finding, or when scope has a finding."
@@ -637,6 +648,130 @@
637
648
  "type": "string"
638
649
  }
639
650
  }
651
+ },
652
+ "links": {
653
+ "type": "object",
654
+ "required": [
655
+ "kinds",
656
+ "commits",
657
+ "findings"
658
+ ],
659
+ "properties": {
660
+ "kinds": {
661
+ "type": "array",
662
+ "items": {
663
+ "type": "string"
664
+ },
665
+ "description": "The work kinds read, by name. A Chant-Record links a commit only when its kind is one of these."
666
+ },
667
+ "commits": {
668
+ "type": "array",
669
+ "description": "Oldest first.",
670
+ "items": {
671
+ "$ref": "#/$defs/linkCommit"
672
+ }
673
+ },
674
+ "findings": {
675
+ "type": "array",
676
+ "items": {
677
+ "$ref": "#/$defs/linkFinding"
678
+ }
679
+ }
680
+ }
681
+ },
682
+ "linkCommit": {
683
+ "type": "object",
684
+ "required": [
685
+ "commit",
686
+ "subject",
687
+ "state",
688
+ "ids",
689
+ "problems"
690
+ ],
691
+ "properties": {
692
+ "commit": {
693
+ "type": "string",
694
+ "pattern": "^[0-9a-f]{40,64}$"
695
+ },
696
+ "subject": {
697
+ "type": "string"
698
+ },
699
+ "state": {
700
+ "enum": [
701
+ "linked",
702
+ "unlinked",
703
+ "wrong"
704
+ ],
705
+ "description": "linked: a trailer names a work record that exists, or a lease that resolves to one (a lease is taken as linked when this checkout has no chant/lifecycle branch to read). unlinked: no Chant-Record or Chant-Lease trailer names anything. wrong: trailers name a work item but none of them is one that exists."
706
+ },
707
+ "ids": {
708
+ "type": "array",
709
+ "items": {
710
+ "type": "string"
711
+ },
712
+ "description": "The work records the trailers name, as <kind>:<id>. Empty unless state is linked."
713
+ },
714
+ "problems": {
715
+ "type": "array",
716
+ "description": "The trailers that do not link the commit. Empty unless state is wrong.",
717
+ "items": {
718
+ "type": "object",
719
+ "required": [
720
+ "trailer",
721
+ "message"
722
+ ],
723
+ "properties": {
724
+ "trailer": {
725
+ "type": "string",
726
+ "description": "The trailer as written, such as Chant-Record: decision:ws-075."
727
+ },
728
+ "message": {
729
+ "type": "string"
730
+ }
731
+ }
732
+ }
733
+ }
734
+ }
735
+ },
736
+ "linkFinding": {
737
+ "type": "object",
738
+ "required": [
739
+ "id",
740
+ "code",
741
+ "commit",
742
+ "message",
743
+ "fix",
744
+ "severity"
745
+ ],
746
+ "properties": {
747
+ "id": {
748
+ "type": "string",
749
+ "description": "finding:<code>:<commit>."
750
+ },
751
+ "code": {
752
+ "enum": [
753
+ "commit-unlinked",
754
+ "commit-wrongly-linked"
755
+ ]
756
+ },
757
+ "commit": {
758
+ "type": "string",
759
+ "pattern": "^[0-9a-f]{40,64}$"
760
+ },
761
+ "message": {
762
+ "type": "string"
763
+ },
764
+ "fix": {
765
+ "type": "string",
766
+ "description": "What to change so the commit names its work item."
767
+ },
768
+ "severity": {
769
+ "enum": [
770
+ "warn",
771
+ "fail"
772
+ ]
773
+ }
774
+ }
640
775
  }
641
776
  }
642
777
  }
@@ -308,7 +308,7 @@ describe("the forward coverage check (#2773)", () => {
308
308
  const step = await changeCoverage({ cwd: tree });
309
309
  expect(step).toMatchObject({ ok: true, work: "work/W-001", severity: "warn", summary: { paths: 2, covered: 1, uncovered: 1 } });
310
310
  expect(step.findings).toEqual([expect.objectContaining({ code: "change-uncovered", path: "docs/readme.md", triage: { finding: "change-uncovered", region: "docs/readme.md" } })]);
311
- await expect(changeCoverage({ cwd: tree, severity: "fail" })).rejects.toThrow(/1 change finding at severity fail: change-uncovered docs\/readme.md/);
311
+ await expect(changeCoverage({ cwd: tree, severity: "fail" })).rejects.toThrow(/2 change findings at severity fail: change-uncovered docs\/readme.md, commit-unlinked /);
312
312
  await expect(changeCoverage({ cwd: root })).rejects.toThrow(/not on a chant\/work\/ branch/);
313
313
  git(root, "worktree", "remove", "--force", tree);
314
314
  });
@@ -42,8 +42,9 @@ import { relative, resolve, sep } from "node:path";
42
42
  import picomatch from "picomatch";
43
43
  import { declaredRecordKinds, readDeclaration, readerVersion, WORKSPACE_ERROR_CODES, WorkspaceReadError, type ChangeSeverity, type Declaration } from "./declaration";
44
44
  import { declaredKindFile } from "./declared-kinds";
45
+ import { COMMIT_UNLINKED_FIX, hasLeaseLedger, judgeCommits, mergeCommits, type CommitLink, type WorkIndex } from "./commit-links";
45
46
  import { isGeneratedPath } from "./generated-files";
46
- import { parseRegion } from "./intent";
47
+ import { commitDetails, parseRegion } from "./intent";
47
48
  import { loadClassRegistry } from "./principal-classes";
48
49
  import { constraintCovers, isWorkspacePath, memberHolding } from "./record-assets";
49
50
  import { loadRecordKind, RecordReadError, type LoadedRecordKind, type RecordEntry } from "./records";
@@ -69,6 +70,45 @@ export const CHANGES_FINDING_CODES = [
69
70
  ] as const satisfies readonly ReasonCode[];
70
71
  export type ChangesFindingCode = (typeof CHANGES_FINDING_CODES)[number];
71
72
 
73
+ /** The codes of a finding on a commit rather than a path (#4029). Closed. */
74
+ export const COMMIT_LINK_FINDING_CODES = [
75
+ /** A trunk commit in the range names no work item: no Chant-Record of a work kind and no Chant-Lease. */
76
+ "commit-unlinked",
77
+ /** A trunk commit in the range names a work item that is not there: a record id no work kind has, a record of another kind, or a lease that resolves to nothing. */
78
+ "commit-wrongly-linked",
79
+ ] as const satisfies readonly ReasonCode[];
80
+ export type CommitLinkFindingCode = (typeof COMMIT_LINK_FINDING_CODES)[number];
81
+
82
+ /** One non-merge first-parent commit of the range, with whether it names a work item. */
83
+ export interface LinkCommit {
84
+ commit: string;
85
+ subject: string;
86
+ state: Exclude<CommitLink["state"], "merge">;
87
+ /** The work records its trailers name, as `<kind>:<id>`. */
88
+ ids: string[];
89
+ /** The trailers that do not link it, when `state` is `wrong`. */
90
+ problems: CommitLink["problems"];
91
+ }
92
+
93
+ export interface LinkFinding {
94
+ /** `finding:<code>:<commit>`. */
95
+ id: string;
96
+ code: CommitLinkFindingCode;
97
+ commit: string;
98
+ message: string;
99
+ /** What to change so the commit names its work item. */
100
+ fix: string;
101
+ severity: "warn" | "fail";
102
+ }
103
+
104
+ /** Whether the range's commits name their work items (#4029). */
105
+ export interface LinkReport {
106
+ /** The work kinds read, by name. */
107
+ kinds: string[];
108
+ commits: LinkCommit[];
109
+ findings: LinkFinding[];
110
+ }
111
+
72
112
  /** Why the check failed as a whole: the declaration's codes, the record kind's, or a `--work` item no record has. */
73
113
  export const CHANGES_ERROR_CODES = [
74
114
  ...WORKSPACE_ERROR_CODES,
@@ -180,6 +220,12 @@ export type ChangesDocument =
180
220
  * or null when that declaration restricts no one.
181
221
  */
182
222
  scope: ScopeReport | null;
223
+ /**
224
+ * Whether each trunk commit of the range names the work item it belongs
225
+ * to (#4029), or null when no work kind was read. Findings follow the
226
+ * severity, like `findings`.
227
+ */
228
+ links: LinkReport | null;
183
229
  /** False when severity is fail and there are findings, or when a commit writes outside its writer's scope. */
184
230
  ok: boolean;
185
231
  })
@@ -472,6 +518,7 @@ async function run(query: ChangesQuery, head: Head): Promise<Exclude<ChangesDocu
472
518
  }
473
519
  const count = (s: ChangeStatus) => paths.filter((p) => p.status === s).length;
474
520
  const scope = await writeScopeAtBase(query, top, range, workspacePrefix);
521
+ const links = commitLinks(top, range, kinds, severity);
475
522
  return {
476
523
  ...head,
477
524
  workspace: { name: declaration.name, root: located.root },
@@ -484,8 +531,43 @@ async function run(query: ChangesQuery, head: Head): Promise<Exclude<ChangesDocu
484
531
  findings,
485
532
  summary: { paths: paths.length, covered: count("covered"), uncovered: count("uncovered"), outOfScope: count("out-of-scope"), ignored: count("ignored"), records: count("record") },
486
533
  scope,
487
- ok: !(severity === "fail" && findings.length > 0) && (scope === null || scope.findings.length === 0),
534
+ links,
535
+ ok: !(severity === "fail" && (findings.length > 0 || (links?.findings.length ?? 0) > 0)) && (scope === null || scope.findings.length === 0),
536
+ };
537
+ }
538
+
539
+ /**
540
+ * Whether each trunk commit of the range, first-parent and merges left out,
541
+ * names a work item (#4029). Records are read at the head, as the paths are.
542
+ * Null when no work kind was read.
543
+ */
544
+ function commitLinks(top: string, range: { base: string; head: string }, kinds: ReadKind[], severity: ChangeSeverity): LinkReport | null {
545
+ const work = kinds.filter((k) => k.role === "work");
546
+ if (work.length === 0) return null;
547
+ const index: WorkIndex = {
548
+ kinds: new Map(work.map((k) => [k.loaded.kind.name, new Set(k.records.flatMap((r) => (r.id === null ? [] : [r.id])))])),
549
+ ledger: hasLeaseLedger(top),
488
550
  };
551
+ const shas = git(top, ["rev-list", "--first-parent", "--reverse", `${range.base}..${range.head}`]).split("\n").filter(Boolean);
552
+ const judged = judgeCommits(top, shas, index, mergeCommits(top, shas));
553
+ const details = commitDetails(top, shas);
554
+ const commits: LinkCommit[] = [];
555
+ const findings: LinkFinding[] = [];
556
+ for (const sha of shas) {
557
+ const link = judged.get(sha)!;
558
+ if (link.state === "merge") continue;
559
+ const subject = details.get(sha)?.subject ?? "";
560
+ commits.push({ commit: sha, subject, state: link.state, ids: link.ids, problems: link.problems });
561
+ if (link.state === "linked" || severity === "off") continue;
562
+ const short = sha.slice(0, 8);
563
+ if (link.state === "unlinked") {
564
+ findings.push({ id: `finding:commit-unlinked:${sha}`, code: "commit-unlinked", commit: sha, severity, message: `${short} "${subject}" carries no Chant-Record trailer of a work kind and no Chant-Lease trailer, so it names no work item`, fix: COMMIT_UNLINKED_FIX });
565
+ } else {
566
+ const named = link.problems.map((p) => `${p.trailer} (${p.message})`).join("; ");
567
+ findings.push({ id: `finding:commit-wrongly-linked:${sha}`, code: "commit-wrongly-linked", commit: sha, severity, message: `${short} "${subject}" names no work item that exists: ${named}`, fix: `correct the trailer to name an existing work record, \`Chant-Record: ${[...index.kinds.keys()][0]}:<id>\`, or a lease taken on one` });
568
+ }
569
+ }
570
+ return { kinds: [...index.kinds.keys()], commits, findings };
489
571
  }
490
572
 
491
573
  /**