@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,123 @@
1
+ /**
2
+ * The statement a ci tag carries (#4031): a runner's signed word that a
3
+ * commit went green, or went red after it was green.
4
+ *
5
+ * `ci/green/<sha>` and `ci/revoked/<sha>` are plain git tags (ws-103), so
6
+ * anyone who may push a tag could make one by hand and go around CI. When the
7
+ * declaration sets `ci.green.signedSince` to a commit, a tag on any commit
8
+ * that is not a strict first-parent ancestor of it counts only when its
9
+ * annotation holds a statement signed by a runner key, and readers ignore
10
+ * and report a tag without a valid one.
11
+ *
12
+ * The statement reuses what runner evidence (#2553, ws-069) and the agent-run
13
+ * statement (#3192, ws-090) already use: an in-toto Statement v1 in a DSSE
14
+ * envelope (./dsse.ts), signed with an Ed25519 key that `.chant/trust.json`
15
+ * at base lists under `runners`. The key's principal must also hold the
16
+ * `runner` role there, since a tag says CI ran. A key the signers file lists
17
+ * is never a runner key (./policy.ts).
18
+ *
19
+ * - `subject`: the tagged commit, by `digest.gitCommit`.
20
+ * - `predicate`: the signer's principal and the tag's JSON record as the
21
+ * annotation holds it (`tag`, `commit`, `branch`, `at`, and the phases'
22
+ * runs for a green tag or the failed phase and run for a revoked one).
23
+ *
24
+ * The envelope sits in the annotation's JSON record as `statement`, beside
25
+ * the fields it signs, so a reader that does not know it reads the record as
26
+ * before. Verifying needs the tag, the envelope and the runner keys at base,
27
+ * so it works offline, and every read verifies again.
28
+ */
29
+ import { z } from "zod";
30
+ import { canonicalJson } from "../../effect-receipt.js";
31
+ import { IN_TOTO_PAYLOAD_TYPE, signEnvelope, verifyEnvelope } from "./dsse.js";
32
+ import { STATEMENT_TYPE } from "./evidence.js";
33
+ export const CI_TAG_PREDICATE = "https://intentius.io/chant/ci-tag/v1";
34
+ /** The role a tag's signer must hold in `.chant/trust.json` (#4031): the core class `runner`. */
35
+ export const CI_TAG_SIGNER_ROLE = "runner";
36
+ const fullCommit = z.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/);
37
+ const text = z.string().min(1);
38
+ /** The statement a ci tag carries. Strict at the top, so a verifier never acts on a field it does not know. */
39
+ export const ciTagStatementSchema = z
40
+ .object({
41
+ _type: z.literal(STATEMENT_TYPE),
42
+ subject: z
43
+ .array(z
44
+ .object({ name: fullCommit, digest: z.object({ gitCommit: fullCommit }).strict() })
45
+ .strict()
46
+ .refine((s) => s.name === s.digest.gitCommit, "a commit subject is named by its own id"))
47
+ .length(1),
48
+ predicateType: z.literal(CI_TAG_PREDICATE),
49
+ predicate: z
50
+ .object({
51
+ signer: text,
52
+ record: z
53
+ .object({ schema: z.literal(1), tag: z.enum(["green", "revoked"]), commit: fullCommit, branch: text, at: text })
54
+ .passthrough(),
55
+ })
56
+ .strict(),
57
+ })
58
+ .strict();
59
+ export function buildCiTagStatement(record, signer) {
60
+ return {
61
+ _type: STATEMENT_TYPE,
62
+ subject: [{ name: record.commit, digest: { gitCommit: record.commit } }],
63
+ predicateType: CI_TAG_PREDICATE,
64
+ predicate: { signer, record: record },
65
+ };
66
+ }
67
+ /** The envelope a tag's record carries as `statement`, signed by `signer`. */
68
+ export function signCiTagRecord(record, signer) {
69
+ const statement = buildCiTagStatement(record, signer.principal);
70
+ return signEnvelope(IN_TOTO_PAYLOAD_TYPE, Buffer.from(canonicalJson(statement), "utf8"), signer.key, signer.publicKey);
71
+ }
72
+ const normalise = (name) => name.normalize("NFKC").trim().toLowerCase();
73
+ /**
74
+ * Verify the statement in a tag's record. `expect` is what the tag itself
75
+ * says: the commit its name gives, its kind and the declared branch. The
76
+ * signer's key must be a runner key at base and its principal must hold the
77
+ * `runner` role in `roles`. Never throws.
78
+ */
79
+ export function verifyCiTagStatement(record, expect, runners, roles) {
80
+ const tag = `ci/${expect.kind}/${expect.sha}`;
81
+ if (record === null)
82
+ return { status: "unsigned", reason: `${tag} has no JSON record in its annotation, so it carries no statement (a lightweight tag, or one made by hand)` };
83
+ const { statement: envelope, ...fields } = record;
84
+ if (envelope === undefined)
85
+ return { status: "unsigned", reason: `${tag} carries no statement signed by a runner key` };
86
+ const v = verifyEnvelope(envelope, runners);
87
+ if (!v.ok)
88
+ return { status: v.code === "envelope-untrusted" ? "untrusted" : "invalid", reason: `${tag}: ${v.reason}` };
89
+ if (!(roles[CI_TAG_SIGNER_ROLE] ?? []).some((p) => normalise(p) === normalise(v.principal))) {
90
+ return { status: "untrusted", reason: `${tag} is signed by ${v.principal}'s runner key, and ${v.principal} holds no ${CI_TAG_SIGNER_ROLE} role in .chant/trust.json at base` };
91
+ }
92
+ if (v.payloadType !== IN_TOTO_PAYLOAD_TYPE)
93
+ return { status: "invalid", reason: `${tag}: payload type ${v.payloadType} is not ${IN_TOTO_PAYLOAD_TYPE}` };
94
+ let raw;
95
+ try {
96
+ raw = JSON.parse(v.payload.toString("utf8"));
97
+ }
98
+ catch {
99
+ return { status: "invalid", reason: `${tag}: the statement's payload is not JSON` };
100
+ }
101
+ const parsed = ciTagStatementSchema.safeParse(raw);
102
+ if (!parsed.success) {
103
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "/"}: ${i.message}`).join("; ");
104
+ return { status: "invalid", reason: `${tag}: the payload is not a ${CI_TAG_PREDICATE} statement: ${detail}` };
105
+ }
106
+ const p = parsed.data.predicate;
107
+ if (p.signer !== v.principal)
108
+ return { status: "invalid", reason: `${tag}: the statement names signer ${JSON.stringify(p.signer)}, but ${v.principal}'s key signed it` };
109
+ const differs = [];
110
+ if (parsed.data.subject[0].name !== expect.sha)
111
+ differs.push(`it is for commit ${parsed.data.subject[0].name.slice(0, 12)}`);
112
+ if (p.record.commit !== expect.sha)
113
+ differs.push(`its record names commit ${p.record.commit.slice(0, 12)}`);
114
+ if (p.record.tag !== expect.kind)
115
+ differs.push(`it signs a ${p.record.tag} tag`);
116
+ if (p.record.branch !== expect.branch)
117
+ differs.push(`it is for branch ${p.record.branch}, and ci.green names ${expect.branch}`);
118
+ if (canonicalJson(p.record) !== canonicalJson(fields))
119
+ differs.push("the annotation's record is not the one signed");
120
+ if (differs.length > 0)
121
+ return { status: "mismatch", reason: `${tag} is signed by ${v.principal}, but the statement does not match the tag: ${differs.join("; ")}` };
122
+ return { status: "signed", signer: v.principal, keyid: v.keyid, reason: `signed by ${v.principal} (${CI_TAG_SIGNER_ROLE})` };
123
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The Op-run statement (#4044): a runner's signed word that an Op ran on a
3
+ * commit and settled as its run record says.
4
+ *
5
+ * `chant ci tick` can take chant's own Op runs as its status source in place
6
+ * of a forge's check runs (`ci.green.source: "runs"`), so a project whose
7
+ * pipelines run as chant Ops gets green and revoked tags with no forge check
8
+ * runs. The run ledger on `chant/lifecycle` is written by whatever ran the
9
+ * Op, so the tick counts a run only when its record carries a statement
10
+ * signed by a runner key, the same keys and role a ci tag needs (#4031):
11
+ * `.chant/trust.json` at base lists the key under `runners`, and its
12
+ * principal holds the `runner` role there. A key the signers file lists is
13
+ * never a runner key (./policy.ts).
14
+ *
15
+ * The statement is an in-toto Statement v1 in a DSSE envelope (./dsse.ts),
16
+ * as runner evidence (#2553), the agent-run statement (#3192) and the ci tag
17
+ * statement (#4031) are:
18
+ *
19
+ * - `subject`: the commit the run built from (`gitSha`), by `digest.gitCommit`.
20
+ * - `predicate`: the signer's principal, and the run's id, Op, env, status
21
+ * and the SHA-256 of its record without the statement.
22
+ *
23
+ * The runner signs at the end of the run when it is given a key
24
+ * (`chant run <op> --key <file>` or `CHANT_RUNNER_KEY`), and the envelope is
25
+ * stored on the record itself as `statement`, so the record and its
26
+ * signature are one ledger line. Verifying needs the record and the runner
27
+ * keys at base, so it works offline, and every read verifies again.
28
+ */
29
+ import { z } from "zod";
30
+ import type { OpRunRecord } from "../../op/runtime.js";
31
+ import { type DsseEnvelope } from "./dsse.js";
32
+ import { type CiTagFailure, type CiTagSigner } from "./ci-tag-statement.js";
33
+ import type { RunnerKey } from "./policy.js";
34
+ export declare const OP_RUN_PREDICATE = "https://intentius.io/chant/op-run/v1";
35
+ /** The statement a runner signs for an Op run. Strict, so a verifier never acts on a field it does not know. */
36
+ export declare const opRunStatementSchema: z.ZodObject<{
37
+ _type: z.ZodLiteral<"https://in-toto.io/Statement/v1">;
38
+ subject: z.ZodArray<z.ZodObject<{
39
+ name: z.ZodString;
40
+ digest: z.ZodObject<{
41
+ gitCommit: z.ZodString;
42
+ }, z.core.$strict>;
43
+ }, z.core.$strict>>;
44
+ predicateType: z.ZodLiteral<"https://intentius.io/chant/op-run/v1">;
45
+ predicate: z.ZodObject<{
46
+ signer: z.ZodString;
47
+ run: z.ZodObject<{
48
+ id: z.ZodString;
49
+ op: z.ZodString;
50
+ env: z.ZodString;
51
+ status: z.ZodEnum<{
52
+ fail: "fail";
53
+ gated: "gated";
54
+ ok: "ok";
55
+ waiting: "waiting";
56
+ }>;
57
+ record: z.ZodObject<{
58
+ sha256: z.ZodString;
59
+ }, z.core.$strict>;
60
+ }, z.core.$strict>;
61
+ }, z.core.$strict>;
62
+ }, z.core.$strict>;
63
+ export type OpRunStatement = z.infer<typeof opRunStatementSchema>;
64
+ /**
65
+ * The SHA-256 of a run record: the canonical JSON (keys sorted, no
66
+ * whitespace) of the record without its `statement`, so storing the
67
+ * statement never changes the hash it signs.
68
+ */
69
+ export declare function opRunRecordDigest(record: OpRunRecord): string;
70
+ /** The statement for a settled run, as `signer` would sign it. Throws for a run that knew no commit. */
71
+ export declare function buildOpRunStatement(record: OpRunRecord, signer: string): OpRunStatement;
72
+ /** The envelope a run record carries as `statement`, signed by `signer`. */
73
+ export declare function signOpRunRecord(record: OpRunRecord, signer: CiTagSigner): DsseEnvelope;
74
+ /** One run record judged: signed by a runner holding the `runner` role, or why not, in the words a ci tag uses. */
75
+ export type OpRunVerdict = {
76
+ status: "signed";
77
+ signer: string;
78
+ keyid: string;
79
+ reason: string;
80
+ } | {
81
+ status: CiTagFailure;
82
+ reason: string;
83
+ };
84
+ /**
85
+ * Verify the statement a run record carries against the runner keys and
86
+ * roles at base: a signature by a runner key whose principal holds the
87
+ * `runner` role, over a statement for this run, its commit and its record as
88
+ * the ledger holds it. Never throws.
89
+ */
90
+ export declare function verifyOpRunStatement(record: OpRunRecord, runners: readonly RunnerKey[], roles: Readonly<Record<string, readonly string[]>>): OpRunVerdict;
91
+ //# sourceMappingURL=op-run-statement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"op-run-statement.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/op-run-statement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAsD,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAC;AAE/F,OAAO,EAAsB,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC7F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,eAAO,MAAM,gBAAgB,yCAAyC,CAAC;AAKvE,gHAAgH;AAChH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;kBA2BtB,CAAC;AACZ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAG7D;AAED,wGAAwG;AACxG,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAWvF;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,YAAY,CAGtF;AAED,mHAAmH;AACnH,MAAM,MAAM,YAAY,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAI1I;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,GAAG,YAAY,CAgCzJ"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * The Op-run statement (#4044): a runner's signed word that an Op ran on a
3
+ * commit and settled as its run record says.
4
+ *
5
+ * `chant ci tick` can take chant's own Op runs as its status source in place
6
+ * of a forge's check runs (`ci.green.source: "runs"`), so a project whose
7
+ * pipelines run as chant Ops gets green and revoked tags with no forge check
8
+ * runs. The run ledger on `chant/lifecycle` is written by whatever ran the
9
+ * Op, so the tick counts a run only when its record carries a statement
10
+ * signed by a runner key, the same keys and role a ci tag needs (#4031):
11
+ * `.chant/trust.json` at base lists the key under `runners`, and its
12
+ * principal holds the `runner` role there. A key the signers file lists is
13
+ * never a runner key (./policy.ts).
14
+ *
15
+ * The statement is an in-toto Statement v1 in a DSSE envelope (./dsse.ts),
16
+ * as runner evidence (#2553), the agent-run statement (#3192) and the ci tag
17
+ * statement (#4031) are:
18
+ *
19
+ * - `subject`: the commit the run built from (`gitSha`), by `digest.gitCommit`.
20
+ * - `predicate`: the signer's principal, and the run's id, Op, env, status
21
+ * and the SHA-256 of its record without the statement.
22
+ *
23
+ * The runner signs at the end of the run when it is given a key
24
+ * (`chant run <op> --key <file>` or `CHANT_RUNNER_KEY`), and the envelope is
25
+ * stored on the record itself as `statement`, so the record and its
26
+ * signature are one ledger line. Verifying needs the record and the runner
27
+ * keys at base, so it works offline, and every read verifies again.
28
+ */
29
+ import { createHash } from "node:crypto";
30
+ import { z } from "zod";
31
+ import { canonicalJson } from "../../effect-receipt.js";
32
+ import { IN_TOTO_PAYLOAD_TYPE, signEnvelope, verifyEnvelope } from "./dsse.js";
33
+ import { STATEMENT_TYPE } from "./evidence.js";
34
+ import { CI_TAG_SIGNER_ROLE } from "./ci-tag-statement.js";
35
+ export const OP_RUN_PREDICATE = "https://intentius.io/chant/op-run/v1";
36
+ const fullCommit = z.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/);
37
+ const text = z.string().min(1);
38
+ /** The statement a runner signs for an Op run. Strict, so a verifier never acts on a field it does not know. */
39
+ export const opRunStatementSchema = z
40
+ .object({
41
+ _type: z.literal(STATEMENT_TYPE),
42
+ subject: z
43
+ .array(z
44
+ .object({ name: fullCommit, digest: z.object({ gitCommit: fullCommit }).strict() })
45
+ .strict()
46
+ .refine((s) => s.name === s.digest.gitCommit, "a commit subject is named by its own id"))
47
+ .length(1),
48
+ predicateType: z.literal(OP_RUN_PREDICATE),
49
+ predicate: z
50
+ .object({
51
+ signer: text,
52
+ run: z
53
+ .object({
54
+ id: text,
55
+ op: text,
56
+ env: text,
57
+ status: z.enum(["ok", "fail", "gated", "waiting"]),
58
+ record: z.object({ sha256: z.string().regex(/^[0-9a-f]{64}$/) }).strict(),
59
+ })
60
+ .strict(),
61
+ })
62
+ .strict(),
63
+ })
64
+ .strict();
65
+ /**
66
+ * The SHA-256 of a run record: the canonical JSON (keys sorted, no
67
+ * whitespace) of the record without its `statement`, so storing the
68
+ * statement never changes the hash it signs.
69
+ */
70
+ export function opRunRecordDigest(record) {
71
+ const { statement: _statement, ...rest } = record;
72
+ return createHash("sha256").update(canonicalJson(rest), "utf8").digest("hex");
73
+ }
74
+ /** The statement for a settled run, as `signer` would sign it. Throws for a run that knew no commit. */
75
+ export function buildOpRunStatement(record, signer) {
76
+ const sha = record.gitSha;
77
+ if (!sha || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(sha)) {
78
+ throw new Error(`run ${record.id} of ${record.op} names no full commit sha, so there is no commit for its statement to sign`);
79
+ }
80
+ return {
81
+ _type: STATEMENT_TYPE,
82
+ subject: [{ name: sha, digest: { gitCommit: sha } }],
83
+ predicateType: OP_RUN_PREDICATE,
84
+ predicate: { signer, run: { id: record.id, op: record.op, env: record.env, status: record.status, record: { sha256: opRunRecordDigest(record) } } },
85
+ };
86
+ }
87
+ /** The envelope a run record carries as `statement`, signed by `signer`. */
88
+ export function signOpRunRecord(record, signer) {
89
+ const statement = buildOpRunStatement(record, signer.principal);
90
+ return signEnvelope(IN_TOTO_PAYLOAD_TYPE, Buffer.from(canonicalJson(statement), "utf8"), signer.key, signer.publicKey);
91
+ }
92
+ const normalise = (name) => name.normalize("NFKC").trim().toLowerCase();
93
+ /**
94
+ * Verify the statement a run record carries against the runner keys and
95
+ * roles at base: a signature by a runner key whose principal holds the
96
+ * `runner` role, over a statement for this run, its commit and its record as
97
+ * the ledger holds it. Never throws.
98
+ */
99
+ export function verifyOpRunStatement(record, runners, roles) {
100
+ const run = `run ${record.id} of ${record.op}`;
101
+ if (record.statement === undefined)
102
+ return { status: "unsigned", reason: `${run} carries no statement signed by a runner key` };
103
+ const v = verifyEnvelope(record.statement, runners);
104
+ if (!v.ok)
105
+ return { status: v.code === "envelope-untrusted" ? "untrusted" : "invalid", reason: `${run}: ${v.reason}` };
106
+ if (!(roles[CI_TAG_SIGNER_ROLE] ?? []).some((p) => normalise(p) === normalise(v.principal))) {
107
+ return { status: "untrusted", reason: `${run} is signed by ${v.principal}'s runner key, and ${v.principal} holds no ${CI_TAG_SIGNER_ROLE} role in .chant/trust.json at base` };
108
+ }
109
+ if (v.payloadType !== IN_TOTO_PAYLOAD_TYPE)
110
+ return { status: "invalid", reason: `${run}: payload type ${v.payloadType} is not ${IN_TOTO_PAYLOAD_TYPE}` };
111
+ let raw;
112
+ try {
113
+ raw = JSON.parse(v.payload.toString("utf8"));
114
+ }
115
+ catch {
116
+ return { status: "invalid", reason: `${run}: the statement's payload is not JSON` };
117
+ }
118
+ const parsed = opRunStatementSchema.safeParse(raw);
119
+ if (!parsed.success) {
120
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "/"}: ${i.message}`).join("; ");
121
+ return { status: "invalid", reason: `${run}: the payload is not a ${OP_RUN_PREDICATE} statement: ${detail}` };
122
+ }
123
+ const p = parsed.data.predicate;
124
+ if (p.signer !== v.principal)
125
+ return { status: "invalid", reason: `${run}: the statement names signer ${JSON.stringify(p.signer)}, but ${v.principal}'s key signed it` };
126
+ const differs = [];
127
+ if (parsed.data.subject[0].name !== record.gitSha)
128
+ differs.push(`it is for commit ${parsed.data.subject[0].name.slice(0, 12)}, and the record names ${record.gitSha ? record.gitSha.slice(0, 12) : "none"}`);
129
+ if (p.run.id !== record.id)
130
+ differs.push(`it is for run ${p.run.id}`);
131
+ if (p.run.op !== record.op)
132
+ differs.push(`it is for Op ${p.run.op}`);
133
+ if (p.run.env !== record.env)
134
+ differs.push(`it is for env ${p.run.env}`);
135
+ if (p.run.status !== record.status)
136
+ differs.push(`it says ${p.run.status}, and the record says ${record.status}`);
137
+ const digest = opRunRecordDigest(record);
138
+ if (p.run.record.sha256 !== digest)
139
+ differs.push(`the record hashed ${p.run.record.sha256.slice(0, 12)} when signed and ${digest.slice(0, 12)} now`);
140
+ if (differs.length > 0)
141
+ return { status: "mismatch", reason: `${run} is signed by ${v.principal}, but the statement does not match its record: ${differs.join("; ")}` };
142
+ return { status: "signed", signer: v.principal, keyid: v.keyid, reason: `signed by ${v.principal} (${CI_TAG_SIGNER_ROLE})` };
143
+ }
@@ -108,8 +108,13 @@ export declare function fileAt(repo: string, commit: string, path: string): stri
108
108
  export declare function signerHistory(repo: string, base: string, signersPath: string): SignerHistory;
109
109
  /**
110
110
  * Finds the signer set in effect for a commit, by its position in the base's
111
- * history. Built once per base; the first-parent line is read once and
112
- * each lookup is a binary search.
111
+ * history. Built once per base. The first-parent line and the parents of
112
+ * every commit the base reaches are read once, in two `git rev-list` walks,
113
+ * and each lookup of a full commit id is then a map read (#4032). Before,
114
+ * each lookup ran `git merge-base --is-ancestor` about log2(n) times, and a
115
+ * trace over a busy file spent most of its time starting those processes. A
116
+ * commit named any other way (a ref, an abbreviated id) is still looked up
117
+ * with `git merge-base`, as before.
113
118
  */
114
119
  export declare class SignerPositions {
115
120
  private readonly repo;
@@ -118,10 +123,21 @@ export declare class SignerPositions {
118
123
  private line;
119
124
  private index;
120
125
  private memo;
126
+ private entries;
121
127
  constructor(repo: string, base: string, history: SignerHistory);
122
128
  private isAncestor;
123
129
  /** The base's first-parent line, newest first. */
124
130
  private firstParents;
131
+ /**
132
+ * For every commit the base reaches, the index on the first-parent line
133
+ * (newest first) of the oldest first-parent commit that contains it: where
134
+ * it came in. The line is walked oldest first, and each first-parent commit
135
+ * marks what it reaches that nothing older reached. A marked commit's
136
+ * ancestors are all marked, so the walk stops at one.
137
+ */
138
+ private entryIndex;
139
+ /** Where `commit` came in on the first-parent line, or null when the base doesn't reach it. */
140
+ private entryOf;
125
141
  /**
126
142
  * The version that judges `commit`, or null when no signer set was in
127
143
  * effect there (before the first one, or the chain is broken).
@@ -1 +1 @@
1
- {"version":3,"file":"rotation.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/rotation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rotation.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/rotation.ts"],"names":[],"mappings":"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"}
@@ -218,10 +218,17 @@ export function signerHistory(repo, base, signersPath) {
218
218
  }
219
219
  return { versions };
220
220
  }
221
+ /** A full commit id, as git prints it: lowercase hex, sha1 or sha256. */
222
+ const FULL_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
221
223
  /**
222
224
  * Finds the signer set in effect for a commit, by its position in the base's
223
- * history. Built once per base; the first-parent line is read once and
224
- * each lookup is a binary search.
225
+ * history. Built once per base. The first-parent line and the parents of
226
+ * every commit the base reaches are read once, in two `git rev-list` walks,
227
+ * and each lookup of a full commit id is then a map read (#4032). Before,
228
+ * each lookup ran `git merge-base --is-ancestor` about log2(n) times, and a
229
+ * trace over a busy file spent most of its time starting those processes. A
230
+ * commit named any other way (a ref, an abbreviated id) is still looked up
231
+ * with `git merge-base`, as before.
225
232
  */
226
233
  export class SignerPositions {
227
234
  constructor(repo, base, history) {
@@ -249,23 +256,44 @@ export class SignerPositions {
249
256
  return this.line;
250
257
  }
251
258
  /**
252
- * The version that judges `commit`, or null when no signer set was in
253
- * effect there (before the first one, or the chain is broken).
259
+ * For every commit the base reaches, the index on the first-parent line
260
+ * (newest first) of the oldest first-parent commit that contains it: where
261
+ * it came in. The line is walked oldest first, and each first-parent commit
262
+ * marks what it reaches that nothing older reached. A marked commit's
263
+ * ancestors are all marked, so the walk stops at one.
254
264
  */
255
- versionFor(commit) {
256
- const hit = this.memo.get(commit);
257
- if (hit !== undefined)
258
- return hit;
259
- const answer = this.lookup(commit);
260
- this.memo.set(commit, answer);
261
- return answer;
265
+ entryIndex() {
266
+ if (!this.entries) {
267
+ const line = this.firstParents();
268
+ const parents = new Map();
269
+ for (const row of git(this.repo, ["rev-list", "--parents", this.base]).split("\n")) {
270
+ const [commit, ...ps] = row.trim().split(" ");
271
+ if (commit)
272
+ parents.set(commit, ps);
273
+ }
274
+ const entries = new Map();
275
+ for (let i = line.length - 1; i >= 0; i--) {
276
+ const stack = [line[i]];
277
+ while (stack.length > 0) {
278
+ const c = stack.pop();
279
+ if (entries.has(c))
280
+ continue;
281
+ entries.set(c, i);
282
+ for (const p of parents.get(c) ?? [])
283
+ if (!entries.has(p))
284
+ stack.push(p);
285
+ }
286
+ }
287
+ this.entries = entries;
288
+ }
289
+ return this.entries;
262
290
  }
263
- lookup(commit) {
264
- if (this.history.broken)
265
- return null;
266
- const latest = this.history.versions.at(-1) ?? null;
291
+ /** Where `commit` came in on the first-parent line, or null when the base doesn't reach it. */
292
+ entryOf(commit) {
293
+ if (FULL_ID.test(commit))
294
+ return this.entryIndex().get(commit) ?? null;
267
295
  if (!this.isAncestor(commit, this.base))
268
- return latest;
296
+ return null;
269
297
  const line = this.firstParents();
270
298
  // The oldest first-parent commit that contains `commit` is where it came in.
271
299
  let lo = 0;
@@ -281,6 +309,27 @@ export class SignerPositions {
281
309
  hi = mid - 1;
282
310
  }
283
311
  }
312
+ return at;
313
+ }
314
+ /**
315
+ * The version that judges `commit`, or null when no signer set was in
316
+ * effect there (before the first one, or the chain is broken).
317
+ */
318
+ versionFor(commit) {
319
+ const hit = this.memo.get(commit);
320
+ if (hit !== undefined)
321
+ return hit;
322
+ const answer = this.lookup(commit);
323
+ this.memo.set(commit, answer);
324
+ return answer;
325
+ }
326
+ lookup(commit) {
327
+ if (this.history.broken)
328
+ return null;
329
+ const latest = this.history.versions.at(-1) ?? null;
330
+ const at = this.entryOf(commit);
331
+ if (at === null)
332
+ return latest;
284
333
  // The newest version made strictly before that commit.
285
334
  let best = null;
286
335
  let bestIndex = Infinity;
@@ -137,4 +137,15 @@ export declare function sealRecord(keyFile: string, r: {
137
137
  * {@link gateSealPayload}. Throws a {@link SealError}.
138
138
  */
139
139
  export declare function sealGateApproval(keyFile: string, a: SealedGateApproval): VerdictSeal;
140
+ /**
141
+ * Sign a release cut (#4031) with the key in `keyFile`, over
142
+ * `releaseSealPayload` in the `chant-release` namespace. The cut line keeps
143
+ * the armored signature; its actor is the signer. Throws a {@link SealError}.
144
+ */
145
+ export declare function sealReleaseCut(keyFile: string, cut: {
146
+ releaseDigest: string;
147
+ label: string | null;
148
+ actor: string;
149
+ timestamp: string;
150
+ }): VerdictSeal;
140
151
  //# sourceMappingURL=seal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"seal.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/seal.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"seal.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/seal.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,7 @@ import { homedir, tmpdir } from "node:os";
27
27
  import { join, resolve } from "node:path";
28
28
  import { sshKeygen, verifySshSignature } from "./ssh-commit.js";
29
29
  import { GATE_SEAL_NAMESPACE, gateSealPayload, namespaceAllowed, sshSignatureKey, verifySshSignatureWith } from "./gate-seal.js";
30
+ import { RELEASE_SEAL_NAMESPACE, releaseSealPayload } from "../identity-gate-rule.js";
30
31
  export { GATE_SEAL_NAMESPACE, gateSealPayload, gateSealVersion, sealedFieldsOf } from "./gate-seal.js";
31
32
  /** The ssh signature namespace of a verdict seal. */
32
33
  export const REVIEW_SEAL_NAMESPACE = "chant-review";
@@ -248,6 +249,14 @@ export function sealRecord(keyFile, r) {
248
249
  export function sealGateApproval(keyFile, a) {
249
250
  return sign(keyFile, gateSealPayload(a), GATE_SEAL_NAMESPACE, a.resolvedBy);
250
251
  }
252
+ /**
253
+ * Sign a release cut (#4031) with the key in `keyFile`, over
254
+ * `releaseSealPayload` in the `chant-release` namespace. The cut line keeps
255
+ * the armored signature; its actor is the signer. Throws a {@link SealError}.
256
+ */
257
+ export function sealReleaseCut(keyFile, cut) {
258
+ return sign(keyFile, releaseSealPayload(cut), RELEASE_SEAL_NAMESPACE, cut.actor);
259
+ }
251
260
  function sign(keyFile, payload, namespace, signer) {
252
261
  const signed = sshKeygen(["-q", "-Y", "sign", "-n", namespace, "-f", keyFile], payload);
253
262
  if (signed.missing)
@@ -1 +1 @@
1
- {"version":3,"file":"ssh-commit.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/ssh-commit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAsC,cAAc,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAwB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAI7D,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,YAAY,CAsB9E;AAUD,6GAA6G;AAC7G,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAIrI;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAuBjG;AAED,eAAO,MAAM,iBAAiB,EAAE,cAiB/B,CAAC;AAeF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAmBrE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI,CAY/G"}
1
+ {"version":3,"file":"ssh-commit.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/ssh-commit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAsC,cAAc,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAwB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAI7D,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,YAAY,CAsB9E;AAYD,6GAA6G;AAC7G,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAIrI;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAuBjG;AAED,eAAO,MAAM,iBAAiB,EAAE,cAgB/B,CAAC;AAeF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAmBrE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI,CAY/G"}
@@ -44,13 +44,14 @@ export function splitSignedCommit(raw, header = "gpgsig") {
44
44
  const payload = Buffer.concat([Buffer.from(kept.join("\n"), "latin1"), raw.subarray(headEnd)]);
45
45
  return { payload, signature: sig.length > 0 ? sig.join("\n") + "\n" : undefined };
46
46
  }
47
- function objectFormat(repo) {
48
- try {
49
- return execFileSync("git", ["rev-parse", "--show-object-format"], { cwd: repo, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
50
- }
51
- catch {
52
- return "sha1";
53
- }
47
+ /**
48
+ * The signature header of a raw commit object, by its object format, which
49
+ * shows in the length of its tree id. Read from the object itself, so judging
50
+ * a commit starts no `git rev-parse` (#4032).
51
+ */
52
+ function signatureHeader(raw) {
53
+ const tree = /^tree ([0-9a-f]+)$/m.exec(raw.subarray(0, 200).toString("latin1"))?.[1];
54
+ return tree?.length === 64 ? "gpgsig-sha256" : "gpgsig";
54
55
  }
55
56
  /** Run ssh-keygen; `missing` is true when it is not installed. Shared with the verdict seals (./seal.ts). */
56
57
  export function sshKeygen(args, input) {
@@ -101,14 +102,13 @@ export const sshCommitAttestor = {
101
102
  if (hit)
102
103
  return hit;
103
104
  const raw = execFileSync("git", ["cat-file", "commit", commit], { cwd: ctx.repo, maxBuffer: 64 * 1024 * 1024 });
104
- const answer = judgeRaw(raw, objectFormat(ctx.repo) === "sha256" ? "gpgsig-sha256" : "gpgsig", signers);
105
+ const answer = judgeRaw(raw, signatureHeader(raw), signers);
105
106
  cache.set(memo, answer);
106
107
  return answer;
107
108
  },
108
109
  attestRaw(ctx, raw) {
109
110
  // A commit from another repository: its object format shows in its tree line.
110
- const tree = /^tree ([0-9a-f]+)$/m.exec(raw.subarray(0, 200).toString("latin1"))?.[1];
111
- return judgeRaw(raw, tree?.length === 64 ? "gpgsig-sha256" : "gpgsig", ctx.policy.signers);
111
+ return judgeRaw(raw, signatureHeader(raw), ctx.policy.signers);
112
112
  },
113
113
  };
114
114
  function judgeRaw(raw, header, signers) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.124.26",
3
+ "version": "0.124.28",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",