@intentius/chant 0.124.27 → 0.124.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (681) hide show
  1. package/README.md +1 -1
  2. package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
  3. package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
  4. package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  6. package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
  7. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  8. package/dist/__golden__/wave-loop/op-held-on.json +2 -0
  9. package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
  10. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  11. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  12. package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
  13. package/dist/__golden__/wave-loop/op-per-run.json +7 -0
  14. package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
  15. package/dist/__golden__/wave-loop/op-sealed.json +3 -0
  16. package/dist/__golden__/wave-loop/op-spend.json +3 -0
  17. package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
  18. package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
  19. package/dist/build-params.d.ts +34 -1
  20. package/dist/build-params.d.ts.map +1 -1
  21. package/dist/build-params.js +31 -1
  22. package/dist/change-set-graph.d.ts +63 -0
  23. package/dist/change-set-graph.d.ts.map +1 -0
  24. package/dist/change-set-graph.js +130 -0
  25. package/dist/change-set.d.ts +62 -3
  26. package/dist/change-set.d.ts.map +1 -1
  27. package/dist/change-set.js +36 -4
  28. package/dist/chant-version.d.ts +9 -0
  29. package/dist/chant-version.d.ts.map +1 -0
  30. package/dist/chant-version.js +16 -0
  31. package/dist/cli/build-params-cli.d.ts +7 -1
  32. package/dist/cli/build-params-cli.d.ts.map +1 -1
  33. package/dist/cli/build-params-cli.js +17 -8
  34. package/dist/cli/commands/build.d.ts +8 -0
  35. package/dist/cli/commands/build.d.ts.map +1 -1
  36. package/dist/cli/commands/build.js +15 -11
  37. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  38. package/dist/cli/commands/check-lexicon-docs.js +30 -2
  39. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  40. package/dist/cli/commands/check-lexicon.js +13 -2
  41. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
  42. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  43. package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
  44. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  45. package/dist/cli/commands/init-lexicon.js +17 -8
  46. package/dist/cli/handlers/approve.d.ts.map +1 -1
  47. package/dist/cli/handlers/approve.js +64 -4
  48. package/dist/cli/handlers/build.d.ts.map +1 -1
  49. package/dist/cli/handlers/build.js +43 -4
  50. package/dist/cli/handlers/change-set.d.ts +10 -0
  51. package/dist/cli/handlers/change-set.d.ts.map +1 -1
  52. package/dist/cli/handlers/change-set.js +51 -1
  53. package/dist/cli/handlers/fan-out.d.ts +10 -1
  54. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  55. package/dist/cli/handlers/fan-out.js +54 -6
  56. package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
  57. package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
  58. package/dist/cli/handlers/lifecycle-gc.js +143 -0
  59. package/dist/cli/handlers/pr.d.ts +16 -2
  60. package/dist/cli/handlers/pr.d.ts.map +1 -1
  61. package/dist/cli/handlers/pr.js +269 -37
  62. package/dist/cli/handlers/promote.d.ts +7 -0
  63. package/dist/cli/handlers/promote.d.ts.map +1 -1
  64. package/dist/cli/handlers/promote.js +440 -28
  65. package/dist/cli/handlers/release.d.ts +91 -0
  66. package/dist/cli/handlers/release.d.ts.map +1 -0
  67. package/dist/cli/handlers/release.js +589 -0
  68. package/dist/cli/handlers/run.d.ts +38 -2
  69. package/dist/cli/handlers/run.d.ts.map +1 -1
  70. package/dist/cli/handlers/run.js +234 -16
  71. package/dist/cli/main.d.ts.map +1 -1
  72. package/dist/cli/main.js +143 -17
  73. package/dist/cli/mcp/workspace-tools.js +1 -1
  74. package/dist/cli/parse-args.d.ts.map +1 -1
  75. package/dist/cli/parse-args.js +73 -5
  76. package/dist/cli/registry.d.ts +43 -5
  77. package/dist/cli/registry.d.ts.map +1 -1
  78. package/dist/cli/run-events.d.ts +30 -0
  79. package/dist/cli/run-events.d.ts.map +1 -0
  80. package/dist/cli/run-events.js +49 -0
  81. package/dist/cli/version.d.ts +3 -5
  82. package/dist/cli/version.d.ts.map +1 -1
  83. package/dist/cli/version.js +3 -13
  84. package/dist/codegen/docs-in-site.d.ts +71 -0
  85. package/dist/codegen/docs-in-site.d.ts.map +1 -0
  86. package/dist/codegen/docs-in-site.js +177 -0
  87. package/dist/codegen/docs-types.d.ts +14 -1
  88. package/dist/codegen/docs-types.d.ts.map +1 -1
  89. package/dist/codegen/docs.d.ts +18 -1
  90. package/dist/codegen/docs.d.ts.map +1 -1
  91. package/dist/codegen/docs.js +40 -7
  92. package/dist/codegen/rehype-base-url.mjs +15 -9
  93. package/dist/components/auto-release.d.ts +6 -1
  94. package/dist/components/auto-release.d.ts.map +1 -1
  95. package/dist/components/auto-release.js +3 -0
  96. package/dist/components/capability.d.ts +9 -0
  97. package/dist/components/capability.d.ts.map +1 -1
  98. package/dist/components/cli-support.d.ts +6 -0
  99. package/dist/components/cli-support.d.ts.map +1 -1
  100. package/dist/components/cli-support.js +10 -9
  101. package/dist/components/driver.d.ts +50 -9
  102. package/dist/components/driver.d.ts.map +1 -1
  103. package/dist/components/driver.js +116 -54
  104. package/dist/components/fan-out-inputs.d.ts +69 -0
  105. package/dist/components/fan-out-inputs.d.ts.map +1 -0
  106. package/dist/components/fan-out-inputs.js +169 -0
  107. package/dist/components/fan-out-record.d.ts +13 -0
  108. package/dist/components/fan-out-record.d.ts.map +1 -1
  109. package/dist/components/fan-out-record.js +16 -0
  110. package/dist/components/fan-out-run.d.ts +7 -0
  111. package/dist/components/fan-out-run.d.ts.map +1 -1
  112. package/dist/components/fan-out-run.js +85 -26
  113. package/dist/components/fan-out.d.ts +4 -0
  114. package/dist/components/fan-out.d.ts.map +1 -1
  115. package/dist/components/pr-pipeline.d.ts +25 -0
  116. package/dist/components/pr-pipeline.d.ts.map +1 -1
  117. package/dist/components/pr-pipeline.js +22 -4
  118. package/dist/components/pr-run.d.ts +11 -0
  119. package/dist/components/pr-run.d.ts.map +1 -1
  120. package/dist/components/pr-run.js +20 -1
  121. package/dist/components/promote-order.d.ts +38 -0
  122. package/dist/components/promote-order.d.ts.map +1 -0
  123. package/dist/components/promote-order.js +62 -0
  124. package/dist/components/promote.d.ts +38 -2
  125. package/dist/components/promote.d.ts.map +1 -1
  126. package/dist/components/promote.js +77 -2
  127. package/dist/components/release-compile.d.ts +142 -0
  128. package/dist/components/release-compile.d.ts.map +1 -0
  129. package/dist/components/release-compile.js +382 -0
  130. package/dist/components/release-gate.d.ts +135 -0
  131. package/dist/components/release-gate.d.ts.map +1 -0
  132. package/dist/components/release-gate.js +288 -0
  133. package/dist/components/release-promote.d.ts +96 -0
  134. package/dist/components/release-promote.d.ts.map +1 -0
  135. package/dist/components/release-promote.js +204 -0
  136. package/dist/components/run-progress.d.ts +7 -0
  137. package/dist/components/run-progress.d.ts.map +1 -1
  138. package/dist/components/wave-plan.d.ts +8 -1
  139. package/dist/components/wave-plan.d.ts.map +1 -1
  140. package/dist/components/wave-plan.js +11 -3
  141. package/dist/config.d.ts +45 -0
  142. package/dist/config.d.ts.map +1 -1
  143. package/dist/config.js +3 -0
  144. package/dist/flag-params.d.ts +54 -0
  145. package/dist/flag-params.d.ts.map +1 -0
  146. package/dist/flag-params.js +130 -0
  147. package/dist/index.d.ts +1 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +2 -0
  150. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  151. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  152. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  153. package/dist/lifecycle/change-set-store.d.ts +68 -0
  154. package/dist/lifecycle/change-set-store.d.ts.map +1 -0
  155. package/dist/lifecycle/change-set-store.js +97 -0
  156. package/dist/lifecycle/commit-sha.d.ts +13 -0
  157. package/dist/lifecycle/commit-sha.d.ts.map +1 -0
  158. package/dist/lifecycle/commit-sha.js +21 -0
  159. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  160. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  161. package/dist/lifecycle/delivery-metrics.js +145 -0
  162. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  163. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  164. package/dist/lifecycle/deploy-outcome.js +101 -0
  165. package/dist/lifecycle/gate-change-set.d.ts +78 -0
  166. package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
  167. package/dist/lifecycle/gate-change-set.js +138 -0
  168. package/dist/lifecycle/gate-ledger.d.ts +51 -4
  169. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  170. package/dist/lifecycle/gate-ledger.js +18 -0
  171. package/dist/lifecycle/git.d.ts +30 -1
  172. package/dist/lifecycle/git.d.ts.map +1 -1
  173. package/dist/lifecycle/git.js +31 -5
  174. package/dist/lifecycle/index.d.ts +4 -0
  175. package/dist/lifecycle/index.d.ts.map +1 -1
  176. package/dist/lifecycle/index.js +4 -0
  177. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  178. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  179. package/dist/lifecycle/mixed-environment.js +154 -0
  180. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  181. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  182. package/dist/lifecycle/publish-attachments.js +179 -0
  183. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  184. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  185. package/dist/lifecycle/publish-credentials.js +69 -0
  186. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  187. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  188. package/dist/lifecycle/publish-declaration.js +83 -0
  189. package/dist/lifecycle/publish-forge.d.ts +53 -0
  190. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  191. package/dist/lifecycle/publish-forge.js +298 -0
  192. package/dist/lifecycle/publish-npm.d.ts +61 -0
  193. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  194. package/dist/lifecycle/publish-npm.js +242 -0
  195. package/dist/lifecycle/publish-oci.d.ts +64 -0
  196. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  197. package/dist/lifecycle/publish-oci.js +383 -0
  198. package/dist/lifecycle/publish-record.d.ts +66 -0
  199. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  200. package/dist/lifecycle/publish-record.js +84 -0
  201. package/dist/lifecycle/publish-targets.d.ts +45 -0
  202. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  203. package/dist/lifecycle/publish-targets.js +85 -0
  204. package/dist/lifecycle/release-cut.d.ts +154 -0
  205. package/dist/lifecycle/release-cut.d.ts.map +1 -0
  206. package/dist/lifecycle/release-cut.js +366 -0
  207. package/dist/lifecycle/release-join.d.ts +95 -0
  208. package/dist/lifecycle/release-join.d.ts.map +1 -0
  209. package/dist/lifecycle/release-join.js +139 -0
  210. package/dist/lifecycle/release-ledger.d.ts +54 -0
  211. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  212. package/dist/lifecycle/release-ledger.js +53 -20
  213. package/dist/lifecycle/release-publish.d.ts +312 -0
  214. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  215. package/dist/lifecycle/release-publish.js +335 -0
  216. package/dist/lifecycle/release-revocation.d.ts +80 -0
  217. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  218. package/dist/lifecycle/release-revocation.js +123 -0
  219. package/dist/lifecycle/release-store.d.ts +200 -0
  220. package/dist/lifecycle/release-store.d.ts.map +1 -0
  221. package/dist/lifecycle/release-store.js +320 -0
  222. package/dist/lifecycle/release-verify.d.ts +61 -0
  223. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  224. package/dist/lifecycle/release-verify.js +92 -0
  225. package/dist/lifecycle/release-workstation.d.ts +91 -0
  226. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  227. package/dist/lifecycle/release-workstation.js +180 -0
  228. package/dist/lifecycle/retention-policy.d.ts +45 -0
  229. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  230. package/dist/lifecycle/retention-policy.js +43 -0
  231. package/dist/lifecycle/retention.d.ts +185 -0
  232. package/dist/lifecycle/retention.d.ts.map +1 -0
  233. package/dist/lifecycle/retention.js +400 -0
  234. package/dist/lifecycle/run-ledger.d.ts +4 -0
  235. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  236. package/dist/lifecycle/run-ledger.js +2 -0
  237. package/dist/op/activities/change-coverage.d.ts.map +1 -1
  238. package/dist/op/activities/change-coverage.js +4 -2
  239. package/dist/op/activities/source-release.d.ts.map +1 -1
  240. package/dist/op/activities/source-release.js +7 -3
  241. package/dist/op/activities/source-rollback.d.ts.map +1 -1
  242. package/dist/op/activities/source-rollback.js +7 -3
  243. package/dist/op/gate-name.d.ts +11 -2
  244. package/dist/op/gate-name.d.ts.map +1 -1
  245. package/dist/op/gate-name.js +12 -3
  246. package/dist/op/gate-run-locator.d.ts +22 -0
  247. package/dist/op/gate-run-locator.d.ts.map +1 -1
  248. package/dist/op/gate-run-locator.js +31 -0
  249. package/dist/op/gate.d.ts +27 -3
  250. package/dist/op/gate.d.ts.map +1 -1
  251. package/dist/op/gate.js +57 -3
  252. package/dist/op/local-executor.d.ts +36 -1
  253. package/dist/op/local-executor.d.ts.map +1 -1
  254. package/dist/op/local-executor.js +158 -52
  255. package/dist/op/op-waves-run.d.ts.map +1 -1
  256. package/dist/op/op-waves-run.js +4 -0
  257. package/dist/op/run-live.d.ts +2 -0
  258. package/dist/op/run-live.d.ts.map +1 -1
  259. package/dist/op/run-live.js +1 -1
  260. package/dist/op/runtime.d.ts +46 -0
  261. package/dist/op/runtime.d.ts.map +1 -1
  262. package/dist/op/runtimes/local.d.ts.map +1 -1
  263. package/dist/op/runtimes/local.js +8 -0
  264. package/dist/plan-summary.d.ts.map +1 -1
  265. package/dist/plan-summary.js +8 -2
  266. package/dist/point-decision.d.ts +2 -0
  267. package/dist/point-decision.d.ts.map +1 -1
  268. package/dist/point-decision.js +9 -2
  269. package/dist/pr-loop.d.ts +7 -0
  270. package/dist/pr-loop.d.ts.map +1 -1
  271. package/dist/pr-loop.js +23 -4
  272. package/dist/pr-report-url.d.ts +82 -0
  273. package/dist/pr-report-url.d.ts.map +1 -0
  274. package/dist/pr-report-url.js +174 -0
  275. package/dist/provenance.d.ts +2 -2
  276. package/dist/provenance.d.ts.map +1 -1
  277. package/dist/run-event-store.d.ts +104 -0
  278. package/dist/run-event-store.d.ts.map +1 -0
  279. package/dist/run-event-store.js +251 -0
  280. package/dist/run-event-stream.d.ts +133 -0
  281. package/dist/run-event-stream.d.ts.map +1 -0
  282. package/dist/run-event-stream.js +242 -0
  283. package/dist/run-event.d.ts +285 -0
  284. package/dist/run-event.d.ts.map +1 -0
  285. package/dist/run-event.js +209 -0
  286. package/dist/wave-runtime.mjs +262 -226
  287. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  288. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  289. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  290. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  291. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  292. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  293. package/dist/workspace/change-set-show.d.ts +56 -0
  294. package/dist/workspace/change-set-show.d.ts.map +1 -0
  295. package/dist/workspace/change-set-show.js +54 -0
  296. package/dist/workspace/change-set-show.schema.json +102 -0
  297. package/dist/workspace/change-set.schema.json +32 -1
  298. package/dist/workspace/changes-cli.d.ts.map +1 -1
  299. package/dist/workspace/changes-cli.js +10 -1
  300. package/dist/workspace/changes.d.ts +37 -0
  301. package/dist/workspace/changes.d.ts.map +1 -1
  302. package/dist/workspace/changes.js +49 -2
  303. package/dist/workspace/changes.schema.json +135 -0
  304. package/dist/workspace/check.schema.json +4 -2
  305. package/dist/workspace/checks/records.d.ts +9 -1
  306. package/dist/workspace/checks/records.d.ts.map +1 -1
  307. package/dist/workspace/checks/records.js +35 -1
  308. package/dist/workspace/checks.d.ts +1 -0
  309. package/dist/workspace/checks.d.ts.map +1 -1
  310. package/dist/workspace/checks.js +2 -1
  311. package/dist/workspace/ci-green-cli.d.ts +19 -4
  312. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  313. package/dist/workspace/ci-green-cli.js +75 -12
  314. package/dist/workspace/ci-green-forge.d.ts +20 -3
  315. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  316. package/dist/workspace/ci-green-forge.js +6 -4
  317. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  318. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  319. package/dist/workspace/ci-green-workflow.js +6 -0
  320. package/dist/workspace/ci-green.d.ts +281 -13
  321. package/dist/workspace/ci-green.d.ts.map +1 -1
  322. package/dist/workspace/ci-green.js +456 -38
  323. package/dist/workspace/ci-last-green.schema.json +32 -2
  324. package/dist/workspace/ci-list.schema.json +525 -0
  325. package/dist/workspace/commit-links.d.ts +85 -0
  326. package/dist/workspace/commit-links.d.ts.map +1 -0
  327. package/dist/workspace/commit-links.js +163 -0
  328. package/dist/workspace/declaration.d.ts +35 -0
  329. package/dist/workspace/declaration.d.ts.map +1 -1
  330. package/dist/workspace/declaration.js +27 -1
  331. package/dist/workspace/declaration.schema.json +76 -0
  332. package/dist/workspace/declared-kinds.d.ts +16 -0
  333. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  334. package/dist/workspace/declared-kinds.js +25 -0
  335. package/dist/workspace/delivery-metrics.d.ts +91 -0
  336. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  337. package/dist/workspace/delivery-metrics.js +217 -0
  338. package/dist/workspace/delivery-metrics.schema.json +519 -0
  339. package/dist/workspace/flags-read.d.ts +37 -0
  340. package/dist/workspace/flags-read.d.ts.map +1 -0
  341. package/dist/workspace/flags-read.js +103 -0
  342. package/dist/workspace/flags.d.ts +100 -0
  343. package/dist/workspace/flags.d.ts.map +1 -0
  344. package/dist/workspace/flags.js +98 -0
  345. package/dist/workspace/graph-cli.js +1 -1
  346. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  347. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  348. package/dist/workspace/identity-gate-rule.js +72 -7
  349. package/dist/workspace/identity.d.ts +12 -0
  350. package/dist/workspace/identity.d.ts.map +1 -1
  351. package/dist/workspace/identity.js +13 -1
  352. package/dist/workspace/intent-cli.d.ts +11 -2
  353. package/dist/workspace/intent-cli.d.ts.map +1 -1
  354. package/dist/workspace/intent-cli.js +54 -5
  355. package/dist/workspace/intent-trace.d.ts +219 -0
  356. package/dist/workspace/intent-trace.d.ts.map +1 -0
  357. package/dist/workspace/intent-trace.js +314 -0
  358. package/dist/workspace/intent.d.ts +36 -7
  359. package/dist/workspace/intent.d.ts.map +1 -1
  360. package/dist/workspace/intent.js +192 -22
  361. package/dist/workspace/intent.schema.json +702 -6
  362. package/dist/workspace/ls.d.ts +4 -0
  363. package/dist/workspace/ls.d.ts.map +1 -1
  364. package/dist/workspace/ls.js +2 -0
  365. package/dist/workspace/ls.schema.json +3 -1
  366. package/dist/workspace/member-pipeline.d.ts +6 -0
  367. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  368. package/dist/workspace/member-pipeline.js +6 -0
  369. package/dist/workspace/overlap.d.ts +146 -0
  370. package/dist/workspace/overlap.d.ts.map +1 -0
  371. package/dist/workspace/overlap.js +276 -0
  372. package/dist/workspace/promotion-order.d.ts +41 -0
  373. package/dist/workspace/promotion-order.d.ts.map +1 -0
  374. package/dist/workspace/promotion-order.js +95 -0
  375. package/dist/workspace/reason-codes.d.ts +4 -0
  376. package/dist/workspace/reason-codes.d.ts.map +1 -1
  377. package/dist/workspace/reason-codes.js +7 -1
  378. package/dist/workspace/records.d.ts +15 -2
  379. package/dist/workspace/records.d.ts.map +1 -1
  380. package/dist/workspace/records.js +32 -0
  381. package/dist/workspace/records.schema.json +21 -2
  382. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  383. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  384. package/dist/workspace/releases.d.ts +81 -0
  385. package/dist/workspace/releases.d.ts.map +1 -0
  386. package/dist/workspace/releases.js +103 -0
  387. package/dist/workspace/releases.schema.json +513 -0
  388. package/dist/workspace/run-event.schema.json +295 -0
  389. package/dist/workspace/status-gates.d.ts +34 -1
  390. package/dist/workspace/status-gates.d.ts.map +1 -1
  391. package/dist/workspace/status-gates.js +31 -2
  392. package/dist/workspace/status-stewards.d.ts +4 -0
  393. package/dist/workspace/status-stewards.d.ts.map +1 -1
  394. package/dist/workspace/status-stewards.js +2 -0
  395. package/dist/workspace/status.d.ts +117 -0
  396. package/dist/workspace/status.d.ts.map +1 -1
  397. package/dist/workspace/status.js +218 -4
  398. package/dist/workspace/status.schema.json +268 -2
  399. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  400. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  401. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  402. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  403. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  404. package/dist/workspace/trust/op-run-statement.js +143 -0
  405. package/dist/workspace/trust/rotation.d.ts +18 -2
  406. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  407. package/dist/workspace/trust/rotation.js +65 -16
  408. package/dist/workspace/trust/seal.d.ts +11 -0
  409. package/dist/workspace/trust/seal.d.ts.map +1 -1
  410. package/dist/workspace/trust/seal.js +9 -0
  411. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  412. package/dist/workspace/trust/ssh-commit.js +10 -10
  413. package/dist/workspace/wip.d.ts.map +1 -1
  414. package/dist/workspace/wip.js +11 -3
  415. package/package.json +1 -1
  416. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  417. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  418. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  419. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  420. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  421. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  422. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  423. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  424. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  425. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  426. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  427. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  428. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  429. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  430. package/src/__golden__/wave-loop/op-spend.json +3 -0
  431. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  432. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  433. package/src/build-params.test.ts +46 -0
  434. package/src/build-params.ts +55 -1
  435. package/src/change-set-graph.test.ts +72 -0
  436. package/src/change-set-graph.ts +141 -0
  437. package/src/change-set.ts +87 -6
  438. package/src/chant-version.ts +16 -0
  439. package/src/cli/build-params-cli.ts +22 -9
  440. package/src/cli/commands/build.ts +25 -11
  441. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  442. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  443. package/src/cli/commands/check-lexicon.ts +14 -2
  444. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  445. package/src/cli/commands/init-lexicon.test.ts +25 -0
  446. package/src/cli/commands/init-lexicon.ts +18 -8
  447. package/src/cli/handlers/approve-release.test.ts +84 -0
  448. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  449. package/src/cli/handlers/approve.ts +65 -2
  450. package/src/cli/handlers/build.test.ts +67 -0
  451. package/src/cli/handlers/build.ts +46 -4
  452. package/src/cli/handlers/change-set.ts +49 -1
  453. package/src/cli/handlers/fan-out.test.ts +228 -1
  454. package/src/cli/handlers/fan-out.ts +53 -6
  455. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  456. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  457. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  458. package/src/cli/handlers/operator.test.ts +37 -0
  459. package/src/cli/handlers/pr.test.ts +341 -0
  460. package/src/cli/handlers/pr.ts +292 -47
  461. package/src/cli/handlers/promote.test.ts +669 -7
  462. package/src/cli/handlers/promote.ts +464 -29
  463. package/src/cli/handlers/release-implicit.e2e.test.ts +161 -0
  464. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  465. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  466. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  467. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  468. package/src/cli/handlers/release.e2e.test.ts +199 -0
  469. package/src/cli/handlers/release.ts +593 -0
  470. package/src/cli/handlers/run-auto-release.test.ts +80 -0
  471. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  472. package/src/cli/handlers/run-release.test.ts +55 -0
  473. package/src/cli/handlers/run.test.ts +30 -0
  474. package/src/cli/handlers/run.ts +243 -18
  475. package/src/cli/main.test.ts +8 -0
  476. package/src/cli/main.ts +143 -17
  477. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  478. package/src/cli/mcp/workspace-tools.ts +1 -1
  479. package/src/cli/parse-args.ts +56 -5
  480. package/src/cli/registry.ts +43 -5
  481. package/src/cli/run-events.ts +57 -0
  482. package/src/cli/version.ts +3 -13
  483. package/src/codegen/docs-in-site.test.ts +166 -0
  484. package/src/codegen/docs-in-site.ts +182 -0
  485. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  486. package/src/codegen/docs-types.ts +14 -1
  487. package/src/codegen/docs.ts +60 -7
  488. package/src/codegen/rehype-base-url.d.mts +3 -1
  489. package/src/codegen/rehype-base-url.mjs +15 -9
  490. package/src/codegen/rehype-base-url.test.ts +34 -0
  491. package/src/components/auto-release.test.ts +30 -0
  492. package/src/components/auto-release.ts +9 -1
  493. package/src/components/capability.ts +9 -0
  494. package/src/components/cli-support.test.ts +82 -0
  495. package/src/components/cli-support.ts +16 -9
  496. package/src/components/driver.test.ts +24 -0
  497. package/src/components/driver.ts +165 -56
  498. package/src/components/fan-out-inputs.test.ts +85 -0
  499. package/src/components/fan-out-inputs.ts +192 -0
  500. package/src/components/fan-out-record.test.ts +10 -0
  501. package/src/components/fan-out-record.ts +19 -0
  502. package/src/components/fan-out-run.ts +104 -35
  503. package/src/components/fan-out.ts +4 -0
  504. package/src/components/pr-pipeline.ts +35 -4
  505. package/src/components/pr-run.ts +30 -1
  506. package/src/components/promote-order.test.ts +90 -0
  507. package/src/components/promote-order.ts +75 -0
  508. package/src/components/promote.test.ts +10 -1
  509. package/src/components/promote.ts +94 -3
  510. package/src/components/release-compile.test.ts +147 -0
  511. package/src/components/release-compile.ts +459 -0
  512. package/src/components/release-gate.test.ts +215 -0
  513. package/src/components/release-gate.ts +355 -0
  514. package/src/components/release-promote.test.ts +159 -0
  515. package/src/components/release-promote.ts +232 -0
  516. package/src/components/run-progress.ts +7 -0
  517. package/src/components/wave-plan.ts +16 -3
  518. package/src/config.ts +15 -1
  519. package/src/flag-params.test.ts +144 -0
  520. package/src/flag-params.ts +134 -0
  521. package/src/index.ts +2 -0
  522. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  523. package/src/lifecycle/change-set-store.test.ts +217 -0
  524. package/src/lifecycle/change-set-store.ts +113 -0
  525. package/src/lifecycle/commit-sha.ts +23 -0
  526. package/src/lifecycle/delivery-metrics.ts +263 -0
  527. package/src/lifecycle/deploy-outcome.ts +147 -0
  528. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  529. package/src/lifecycle/gate-change-set.ts +164 -0
  530. package/src/lifecycle/gate-ledger.test.ts +33 -0
  531. package/src/lifecycle/gate-ledger.ts +68 -3
  532. package/src/lifecycle/git.ts +61 -8
  533. package/src/lifecycle/index.ts +4 -0
  534. package/src/lifecycle/mixed-environment.test.ts +97 -0
  535. package/src/lifecycle/mixed-environment.ts +204 -0
  536. package/src/lifecycle/publish-attachments.ts +196 -0
  537. package/src/lifecycle/publish-credentials.ts +84 -0
  538. package/src/lifecycle/publish-declaration.ts +95 -0
  539. package/src/lifecycle/publish-forge.ts +325 -0
  540. package/src/lifecycle/publish-npm.ts +259 -0
  541. package/src/lifecycle/publish-oci.ts +396 -0
  542. package/src/lifecycle/publish-record.ts +113 -0
  543. package/src/lifecycle/publish-resume.test.ts +168 -0
  544. package/src/lifecycle/publish-targets.test.ts +494 -0
  545. package/src/lifecycle/publish-targets.ts +100 -0
  546. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  547. package/src/lifecycle/release-cut.test.ts +483 -0
  548. package/src/lifecycle/release-cut.ts +503 -0
  549. package/src/lifecycle/release-flag.test.ts +112 -0
  550. package/src/lifecycle/release-join.test.ts +68 -0
  551. package/src/lifecycle/release-join.ts +182 -0
  552. package/src/lifecycle/release-ledger.test.ts +14 -0
  553. package/src/lifecycle/release-ledger.ts +85 -17
  554. package/src/lifecycle/release-publish.test.ts +225 -0
  555. package/src/lifecycle/release-publish.ts +568 -0
  556. package/src/lifecycle/release-revocation.ts +147 -0
  557. package/src/lifecycle/release-store.ts +434 -0
  558. package/src/lifecycle/release-verify.test.ts +110 -0
  559. package/src/lifecycle/release-verify.ts +120 -0
  560. package/src/lifecycle/release-workstation.test.ts +204 -0
  561. package/src/lifecycle/release-workstation.ts +216 -0
  562. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  563. package/src/lifecycle/retention-policy.ts +63 -0
  564. package/src/lifecycle/retention.test.ts +363 -0
  565. package/src/lifecycle/retention.ts +508 -0
  566. package/src/lifecycle/run-ledger.test.ts +10 -0
  567. package/src/lifecycle/run-ledger.ts +6 -0
  568. package/src/op/activities/change-coverage.ts +4 -1
  569. package/src/op/activities/source-release.ts +8 -3
  570. package/src/op/activities/source-rollback.ts +8 -4
  571. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  572. package/src/op/gate-name.ts +13 -3
  573. package/src/op/gate-release.test.ts +126 -0
  574. package/src/op/gate-run-locator.test.ts +110 -0
  575. package/src/op/gate-run-locator.ts +35 -0
  576. package/src/op/gate.ts +88 -6
  577. package/src/op/local-executor.ts +207 -52
  578. package/src/op/op-waves-run.ts +4 -0
  579. package/src/op/run-events.test.ts +166 -0
  580. package/src/op/run-live.ts +1 -1
  581. package/src/op/runtime.ts +39 -0
  582. package/src/op/runtimes/local.test.ts +16 -0
  583. package/src/op/runtimes/local.ts +9 -0
  584. package/src/plan-summary.test.ts +20 -0
  585. package/src/plan-summary.ts +8 -1
  586. package/src/point-decision.ts +12 -2
  587. package/src/pr-loop.ts +25 -4
  588. package/src/pr-report-url.test.ts +131 -0
  589. package/src/pr-report-url.ts +209 -0
  590. package/src/provenance.ts +2 -2
  591. package/src/run-event-store.test.ts +144 -0
  592. package/src/run-event-store.ts +273 -0
  593. package/src/run-event-stream.test.ts +175 -0
  594. package/src/run-event-stream.ts +309 -0
  595. package/src/run-event.test.ts +277 -0
  596. package/src/run-event.ts +471 -0
  597. package/src/wave-loop-golden.test.ts +9 -0
  598. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  599. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  600. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  601. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  602. package/src/workspace/ancestry-index.test.ts +93 -0
  603. package/src/workspace/change-set-show.schema.json +102 -0
  604. package/src/workspace/change-set-show.ts +72 -0
  605. package/src/workspace/change-set.schema.json +32 -1
  606. package/src/workspace/changes-cli.ts +10 -1
  607. package/src/workspace/changes.schema.json +135 -0
  608. package/src/workspace/changes.test.ts +1 -1
  609. package/src/workspace/changes.ts +84 -2
  610. package/src/workspace/check.schema.json +4 -2
  611. package/src/workspace/checks/records.ts +35 -1
  612. package/src/workspace/checks.ts +2 -1
  613. package/src/workspace/ci-green-cli.ts +75 -13
  614. package/src/workspace/ci-green-forge.ts +24 -4
  615. package/src/workspace/ci-green-runs.test.ts +242 -0
  616. package/src/workspace/ci-green-signed.test.ts +302 -0
  617. package/src/workspace/ci-green-workflow.test.ts +16 -0
  618. package/src/workspace/ci-green-workflow.ts +12 -0
  619. package/src/workspace/ci-green.test.ts +131 -2
  620. package/src/workspace/ci-green.ts +635 -42
  621. package/src/workspace/ci-last-green.schema.json +32 -2
  622. package/src/workspace/ci-list.schema.json +525 -0
  623. package/src/workspace/commit-links.test.ts +279 -0
  624. package/src/workspace/commit-links.ts +183 -0
  625. package/src/workspace/declaration.schema.json +76 -0
  626. package/src/workspace/declaration.test.ts +28 -0
  627. package/src/workspace/declaration.ts +60 -2
  628. package/src/workspace/declared-kinds.ts +40 -1
  629. package/src/workspace/delivery-metrics.schema.json +519 -0
  630. package/src/workspace/delivery-metrics.test.ts +236 -0
  631. package/src/workspace/delivery-metrics.ts +286 -0
  632. package/src/workspace/flags-read.ts +121 -0
  633. package/src/workspace/flags.test.ts +141 -0
  634. package/src/workspace/flags.ts +157 -0
  635. package/src/workspace/graph-cli.ts +1 -1
  636. package/src/workspace/identity-gate-rule.ts +91 -7
  637. package/src/workspace/identity.ts +14 -1
  638. package/src/workspace/intent-cli.ts +46 -5
  639. package/src/workspace/intent-trace.test.ts +173 -0
  640. package/src/workspace/intent-trace.ts +484 -0
  641. package/src/workspace/intent.schema.json +702 -6
  642. package/src/workspace/intent.ts +196 -26
  643. package/src/workspace/ls.schema.json +3 -1
  644. package/src/workspace/ls.ts +6 -0
  645. package/src/workspace/member-pipeline.test.ts +17 -0
  646. package/src/workspace/member-pipeline.ts +9 -0
  647. package/src/workspace/older-records.test.ts +412 -0
  648. package/src/workspace/overlap.test.ts +165 -0
  649. package/src/workspace/overlap.ts +340 -0
  650. package/src/workspace/promotion-order.ts +97 -0
  651. package/src/workspace/read-contract.e2e.test.ts +5 -1
  652. package/src/workspace/reason-codes.test.ts +7 -1
  653. package/src/workspace/reason-codes.ts +7 -1
  654. package/src/workspace/record-assets.test.ts +2 -1
  655. package/src/workspace/records-contract.e2e.test.ts +2 -1
  656. package/src/workspace/records.schema.json +21 -2
  657. package/src/workspace/records.ts +36 -2
  658. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  659. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  660. package/src/workspace/releases.schema.json +513 -0
  661. package/src/workspace/releases.test.ts +108 -0
  662. package/src/workspace/releases.ts +133 -0
  663. package/src/workspace/run-event.schema.json +295 -0
  664. package/src/workspace/status-contract.test.ts +81 -1
  665. package/src/workspace/status-gates.ts +64 -1
  666. package/src/workspace/status-mixed.test.ts +152 -0
  667. package/src/workspace/status-revoked.test.ts +153 -0
  668. package/src/workspace/status-stewards.ts +6 -0
  669. package/src/workspace/status.schema.json +268 -2
  670. package/src/workspace/status.ts +334 -4
  671. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  672. package/src/workspace/trust/op-run-statement.ts +148 -0
  673. package/src/workspace/trust/rotation.ts +61 -14
  674. package/src/workspace/trust/seal.ts +10 -0
  675. package/src/workspace/trust/ssh-commit.ts +10 -9
  676. package/src/workspace/wip.ts +11 -2
  677. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  678. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  679. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  680. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  681. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -39,7 +39,10 @@ import type { CommandContext } from "../cli/registry";
39
39
  import { GATES_DIR } from "../lifecycle/gate-ledger";
40
40
  import { readPathSha } from "../lifecycle/git";
41
41
  import { latestPerComponent, readReleaseLedger, type ReleaseRecord } from "../lifecycle/release-ledger";
42
+ import { describeRevocation, revokedCommitsAt, revokedReleaseLookup, type ReleaseRevocation, type RevokedRelease } from "../lifecycle/release-revocation";
42
43
  import { readReleasePlan, type ReleasePlan } from "../lifecycle/plan-ledger";
44
+ import { readDeployOutcomes, type DeployOutcome } from "../lifecycle/deploy-outcome";
45
+ import { mixedEnvironment, releaseRollbackLine, rollbackTarget, type MixedEnvironment, type ReleaseCheck } from "../lifecycle/mixed-environment";
43
46
  import { listWorkLeases, type WorkLeaseState } from "../lifecycle/work-lease";
44
47
  import { readCheckoutHead, worktreeChanges, type CheckoutHead } from "./records-checkout";
45
48
  import { policyView, replicationState, type ReplicationState } from "./wip";
@@ -50,7 +53,12 @@ import { loadKindRegistry, resolveMemberFields, type MemberFields } from "./kind
50
53
  import { factoryView, listingView, plantability, treeBytes, type FactoryView, type ListingView, type Plantable } from "./box-factory";
51
54
  import { declaredRecordKinds, extensionsAt, readDeclaration, readerVersion, WorkspaceReadError, type Declaration, type ErrorLocation, type Member } from "./declaration";
52
55
  import type { ReasonCode } from "./reason-codes";
53
- import { gateAdmissionFrom } from "./identity";
56
+ import type { FlagDecision, FlagValue } from "./flags";
57
+ import type { OverlapChangeSetRef, OverlapReport } from "./overlap";
58
+ import type { WorkLink } from "./work";
59
+ import { readMemberPromotionOrder, type PromotionStep } from "./promotion-order";
60
+ import { gateAdmissionFrom, releaseRuleFrom } from "./identity";
61
+ import { checkReleaseSignature, type GateAdmissionRule, type ReleaseSignatureCheck } from "./identity-gate-rule";
54
62
  import { scopeSource } from "./write-scope";
55
63
  import { GATE_REASON_CODES, readMemberGates, type GateLedgerReader, type StatusGate, type StatusGateLedger } from "./status-gates";
56
64
  import { STEWARD_REASON_CODES, readMemberStewards, type StatusSteward, type StewardReasonCode } from "./status-stewards";
@@ -120,6 +128,14 @@ export interface StatusRelease {
120
128
  runId: string;
121
129
  timestamp: string;
122
130
  actor: string;
131
+ /** Who approved the change at its gate (#1035), when it had one. Null for an ungated release. */
132
+ approver: string | null;
133
+ /** The gate resolution that admitted the release (#4020): the gate and when it was resolved. Null for an ungated release and for one recorded before chant 0.124.28. */
134
+ admission: { op: string; gate: string; resolvedAt: string } | null;
135
+ /** The release this deploy was made from (#4043): the `releaseDigest` of `_releases/<releaseDigest>.json`. Null for a deploy that named no release and for one recorded before chant 0.124.29. */
136
+ releaseDigest: string | null;
137
+ /** The label that release was cut with (#4043), or null when it has none, when no release is named, or when the cut can't be read. Chant 0.124.29 and newer write it. */
138
+ releaseLabel: string | null;
123
139
  /** Read flags, such as `legacy-digest` (#2514). */
124
140
  flags: string[];
125
141
  /**
@@ -131,6 +147,25 @@ export interface StatusRelease {
131
147
  * hasn't fetched yet.
132
148
  */
133
149
  plan: ReleasePlan | null;
150
+ /**
151
+ * Set when the deploy names a release (`releaseDigest` on its record) that
152
+ * was cut from a revoked commit (#4033, ws-105): the revoked tag on that
153
+ * commit, the release and its label, and the line that rolls the component
154
+ * back. Absent otherwise, and from chant older than 0.124.29.
155
+ */
156
+ revoked?: StatusRevokedRelease;
157
+ }
158
+
159
+ /** A release an environment runs that was cut from a revoked commit (#4033). */
160
+ export interface StatusRevokedRelease extends RevokedRelease {
161
+ /**
162
+ * The command that rolls the environment back, run in the member's
163
+ * directory: `chant components rollback <env> --release <label|digest>`
164
+ * naming the newest release the environment ran before this one that is
165
+ * still kept and not revoked (#4035), or `chant components rollback <env>
166
+ * --component <component>` when there is none.
167
+ */
168
+ rollback: string;
134
169
  }
135
170
 
136
171
  export interface StatusLedger {
@@ -148,6 +183,14 @@ export interface StatusEnvironment {
148
183
  /** The latest release of each component in the ledger, sorted by component. */
149
184
  releases: StatusRelease[];
150
185
  reason: { code: StatusReasonCode; message: string } | null;
186
+ /**
187
+ * Set when the environment runs a mix (#4035): a promote, rollback or
188
+ * redeploy left a component failed or gated and nothing has been recorded
189
+ * for it since, or its components' latest records name more than one
190
+ * release. Each component with what it runs, and the lines that settle it.
191
+ * Absent otherwise, and from chant older than 0.124.29.
192
+ */
193
+ mixed?: MixedEnvironment;
151
194
  }
152
195
 
153
196
  export type CompareState = "same" | "differs" | "only-env" | "only-compare";
@@ -185,6 +228,63 @@ export interface StatusMember {
185
228
  stewards: StatusSteward[];
186
229
  /** Why a steward or a last run may be missing from `stewards`. */
187
230
  stewardReasons: { code: StewardReasonCode; message: string }[];
231
+ /**
232
+ * The member's promotion order (#4030): the literal entries of the
233
+ * `environments` array in its chant.config.ts, in order, each with whether
234
+ * it is marked canary. A release moves from each step to the next. `[]`
235
+ * when the config declares no environments, null when it could not be read.
236
+ * Added in contract 1 by chant 0.124.28.
237
+ */
238
+ promotionOrder: PromotionStep[] | null;
239
+ /**
240
+ * The member's release cuts (#4026, ws-104), oldest first, each with
241
+ * whether its signature verifies for its actor against the signers at
242
+ * base and, when the declaration names a `release` gate with a class,
243
+ * whether the actor is in it (#4031). `[]` when none is cut, null when the
244
+ * cuts could not be read. Added in contract 1 by chant 0.124.28.
245
+ */
246
+ releaseCuts: StatusReleaseCut[] | null;
247
+ /**
248
+ * The member's feature flags (#4024): each record of a declared flag kind
249
+ * that names this member, with its value per environment and the work item
250
+ * it hides. Sorted by id. `[]` when none names it, null when the flag
251
+ * records could not be read. Called `featureFlags` because a release
252
+ * record's `flags` already means something else. Added in contract 1 by
253
+ * chant 0.124.29.
254
+ */
255
+ featureFlags: StatusFeatureFlag[] | null;
256
+ }
257
+
258
+ /** One feature flag of a member (#4024), as `status --json` lists it. */
259
+ export interface StatusFeatureFlag {
260
+ /** The flag's name, its record's id. */
261
+ id: string;
262
+ /** The build parameter of the member the flag sets. */
263
+ param: string;
264
+ owner: string;
265
+ /** The value per environment, as the record writes it. An environment it leaves out builds with the parameter's default. */
266
+ values: Record<string, FlagValue>;
267
+ /** The work item the flag hides, with its state (null when no record has it), or null when the record names none. */
268
+ work: WorkLink | null;
269
+ /** Each decision the work item implements, with its state and whether it is confirmed. */
270
+ implements: FlagDecision[];
271
+ /** True when every decision the work item implements is confirmed: the flag is due for removal (check WSP118). */
272
+ stale: boolean;
273
+ /** The kind file and the record file, from the workspace root. */
274
+ kind: string;
275
+ path: string;
276
+ }
277
+
278
+ /** One cut of a member's release (#4031), as `status --json` lists it. */
279
+ export interface StatusReleaseCut {
280
+ releaseDigest: string;
281
+ label: string | null;
282
+ /** The release's commit, or null when its release file can't be read. */
283
+ commit: string | null;
284
+ actor: string;
285
+ timestamp: string;
286
+ signature: string | null;
287
+ signatureCheck: ReleaseSignatureCheck;
188
288
  }
189
289
 
190
290
  /** The most paths `ship.pending.paths` lists; `files` still counts them all. */
@@ -294,6 +394,14 @@ export type StatusDocument =
294
394
  acceptance: StatusAcceptance[];
295
395
  /** How far each ref the box's replicate policy names is from its remote (#3172), read locally; null when no box declares one. */
296
396
  replication: ReplicationState | null;
397
+ /**
398
+ * Overlap between in-flight work items (#4025): pairs of items whose
399
+ * linked commits touch the same files, or that both reach one entry of
400
+ * a change set a pending or approved gate binds. A report, never a
401
+ * lock. Null when no work kind is declared or the work records or
402
+ * history can't be read. Added in contract 1 by chant 0.124.29.
403
+ */
404
+ overlap: OverlapReport | null;
297
405
  summary: { members: number; released: number; unreadable: number; differing: number | null };
298
406
  }
299
407
  | {
@@ -312,12 +420,16 @@ export interface StatusQuery {
312
420
  compareTo?: string;
313
421
  /** Reads one ledger; the lifecycle reader unless a test swaps it. */
314
422
  readLedger?: LedgerReader;
423
+ /** Reads the deploy outcomes beside one ledger (#4035), given the ledger's path; the lifecycle reader unless a test swaps it. */
424
+ readOutcomes?: OutcomeReader;
315
425
  /** Reads one member's gate ledger directory; git unless a test swaps it. */
316
426
  readGates?: GateLedgerReader;
317
427
  /** The instant a gate's expiry is measured against; now by default. */
318
428
  now?: string;
319
429
  /** Reads one member's stewards; the declaration and ledger reader unless a test swaps it. */
320
430
  readStewards?: typeof readMemberStewards;
431
+ /** Reads one member's promotion order from its directory; its chant.config.ts unless a test swaps it. */
432
+ readPromotionOrder?: (dir: string) => Promise<PromotionStep[] | null>;
321
433
  }
322
434
 
323
435
  /** A factory view with the declaration's x- keys on it and on its check, publish and tiers (#3595). */
@@ -412,7 +524,27 @@ async function shipView(member: Member, cwd: string, read: LedgerReader): Promis
412
524
 
413
525
  const defaultReader: LedgerReader = (path, cwd) => readReleaseLedger(path.slice(0, -"/releases.jsonl".length), { cwd });
414
526
 
415
- async function readEnvironment(member: Member, env: string, cwd: string, read: LedgerReader): Promise<StatusEnvironment> {
527
+ /** Reads the deploy outcomes (#4035) in the directory of the ledger at `path`. */
528
+ type OutcomeReader = (path: string, cwd: string) => Promise<DeployOutcome[]>;
529
+
530
+ const defaultOutcomeReader: OutcomeReader = (path, cwd) => readDeployOutcomes(path.slice(0, -"/releases.jsonl".length), { cwd }).catch(() => []);
531
+
532
+ /** A release digest's revocation, for one member, or null (#4033). */
533
+ type RevokedOf = (releaseDigest: string) => Promise<RevokedRelease | null>;
534
+
535
+ /** The per-component rollback line, for a component an environment runs from a revoked release when no earlier release can be named (#4033). */
536
+ export function rollbackLine(env: string, component: string): string {
537
+ return `chant components rollback ${env} --component ${component}`;
538
+ }
539
+
540
+ async function readEnvironment(
541
+ member: Member,
542
+ env: string,
543
+ cwd: string,
544
+ read: LedgerReader,
545
+ revokedOf: RevokedOf = async () => null,
546
+ readOutcomes: OutcomeReader = defaultOutcomeReader,
547
+ ): Promise<StatusEnvironment> {
416
548
  const ledger = await ledgerFor(member, env, cwd);
417
549
  let records: ReleaseRecord[];
418
550
  let malformed: number;
@@ -434,6 +566,37 @@ async function readEnvironment(member: Member, env: string, cwd: string, read: L
434
566
  // member (see plan-ledger.ts's readReleasePlan doc).
435
567
  const planPrefix = ledger.layout === "members" ? `${MEMBERS_DIR}/${member.name}/` : "";
436
568
  const latest = [...latestPerComponent(records).values()].sort((a, b) => (a.component < b.component ? -1 : a.component > b.component ? 1 : 0));
569
+ // #4043: a release item names the label of the release it was deployed from.
570
+ const memberDir = resolve(cwd, member.dir);
571
+ let labels = new Map<string, string>();
572
+ let labelsRead = false;
573
+ const readLabels = async (): Promise<Map<string, string>> => {
574
+ if (labelsRead) return labels;
575
+ labelsRead = true;
576
+ try {
577
+ const { readCuts, labelsByDigest } = await import("../lifecycle/release-store");
578
+ labels = labelsByDigest((await readCuts({ cwd: memberDir })).cuts);
579
+ } catch {
580
+ // The label is a convenience; the digest is the join key.
581
+ }
582
+ return labels;
583
+ };
584
+ if (latest.some((r) => r.releaseDigest !== undefined)) await readLabels();
585
+ // #4035: whether a release a rollback could go back to is kept and not revoked.
586
+ const checks = new Map<string, Promise<ReleaseCheck>>();
587
+ const check = (digest: string): Promise<ReleaseCheck> => {
588
+ let found = checks.get(digest);
589
+ if (!found) {
590
+ found = (async () => {
591
+ const { readRelease } = await import("../lifecycle/release-store");
592
+ const kept = (await readRelease(digest, { cwd: memberDir }).catch(() => null)) !== null;
593
+ return { kept, revoked: kept && (await revokedOf(digest)) !== null };
594
+ })();
595
+ checks.set(digest, found);
596
+ }
597
+ return found;
598
+ };
599
+ const back = { records, check, label: async (digest: string) => (await readLabels()).get(digest) };
437
600
  const releases: StatusRelease[] = await Promise.all(
438
601
  latest.map(async (r) => ({
439
602
  component: r.component,
@@ -443,10 +606,16 @@ async function readEnvironment(member: Member, env: string, cwd: string, read: L
443
606
  runId: r.runId,
444
607
  timestamp: r.timestamp,
445
608
  actor: r.actor,
609
+ approver: r.approver ?? null,
610
+ admission: r.admission ? { op: r.admission.op, gate: r.admission.gate, resolvedAt: r.admission.resolvedAt } : null,
611
+ releaseDigest: r.releaseDigest ?? null,
612
+ releaseLabel: (r.releaseDigest !== undefined ? labels.get(r.releaseDigest) : undefined) ?? null,
446
613
  flags: [...(r.flags ?? [])],
447
614
  plan: await readReleasePlan(r.digest, { cwd, prefix: planPrefix }),
615
+ ...(await revokedItem(r, env, revokedOf, back)),
448
616
  })),
449
617
  );
618
+ const mixed = await mixedEnvironment({ env, records, outcomes: await readOutcomes(ledger.path, cwd), label: back.label, check });
450
619
  return {
451
620
  env,
452
621
  ledger: { ...ledger, shared: false },
@@ -455,9 +624,47 @@ async function readEnvironment(member: Member, env: string, cwd: string, read: L
455
624
  malformed > 0
456
625
  ? { code: "ledger-malformed", message: `${ledger.path}: ${malformed} line${malformed === 1 ? " is" : "s are"} not a release record and ${malformed === 1 ? "was" : "were"} skipped` }
457
626
  : null,
627
+ ...(mixed ? { mixed } : {}),
458
628
  };
459
629
  }
460
630
 
631
+ /**
632
+ * `{ revoked }` when the record names a release cut from a revoked commit
633
+ * (#4033), else nothing. Its rollback line names the newest release the
634
+ * environment ran before this one that is kept and not revoked (#4035).
635
+ */
636
+ async function revokedItem(
637
+ r: ReleaseRecord,
638
+ env: string,
639
+ revokedOf: RevokedOf,
640
+ back: { records: readonly ReleaseRecord[]; check: (digest: string) => Promise<ReleaseCheck>; label: (digest: string) => Promise<string | undefined> },
641
+ ): Promise<{ revoked?: StatusRevokedRelease }> {
642
+ if (r.releaseDigest === undefined) return {};
643
+ const revoked = await revokedOf(r.releaseDigest);
644
+ if (!revoked) return {};
645
+ const target = await rollbackTarget({ records: back.records, exclude: new Set([r.releaseDigest]), before: r.timestamp, check: back.check });
646
+ const rollback = target ? releaseRollbackLine(env, target, await back.label(target)) : rollbackLine(env, r.component);
647
+ return { revoked: { ...revoked, rollback } };
648
+ }
649
+
650
+ /**
651
+ * A release gate whose release was cut from a revoked commit is superseded
652
+ * (#4033): `chant approve` refuses it and the promote refuses the release, so
653
+ * neither an approval nor a pending fact will pass it. An expired gate stays
654
+ * expired. The gate carries the revocation.
655
+ */
656
+ async function supersedeRevokedGates(gates: StatusGate[], revokedOf: RevokedOf): Promise<StatusGate[]> {
657
+ return Promise.all(
658
+ gates.map(async (g) => {
659
+ if (g.releaseDigest === undefined) return g;
660
+ const revoked = await revokedOf(g.releaseDigest);
661
+ if (!revoked) return g;
662
+ const { releaseDigest: _d, label: _l, ...revocation } = revoked;
663
+ return { ...g, state: g.state === "expired" ? g.state : "superseded", revoked: revocation };
664
+ }),
665
+ );
666
+ }
667
+
461
668
  /** The digest two environments are compared on: the input side when there is one. */
462
669
  const compareKey = (r: StatusRelease): string => r.inputDigest ?? r.digest;
463
670
 
@@ -506,10 +713,18 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
506
713
  // #3163: the declaration's identity.gates at base, read once for every member's gates.
507
714
  const ruleSource = scopeSource(found.dir);
508
715
  const rules = (gate: string) => gateAdmissionFrom(ruleSource, gate);
716
+ const releaseRule = releaseRuleFrom(ruleSource).rule;
717
+ // #4033 (ws-105): revoked commits, read from local tags once and only when a release is met.
718
+ // Tags that can't be judged read as none revoked; workspace check reports the declaration.
719
+ let revokedCommits: Promise<ReadonlyMap<string, ReleaseRevocation>> | undefined;
720
+ const revoked = () => (revokedCommits ??= revokedCommitsAt(top, ruleSource.declaration?.ci?.green ?? null).catch(() => new Map()));
721
+ // #4024: the flag records, read once for every member. Unreadable records list as null.
722
+ const flags = await readStatusFlags(declaration, found.dir);
509
723
  const members: StatusMember[] = [];
510
724
  for (const m of declaration.members) {
725
+ const revokedOf = revokedReleaseLookup(resolve(found.dir, m.dir), revoked);
511
726
  const environments: StatusEnvironment[] = [];
512
- for (const env of envs) environments.push(await readEnvironment(m, env, found.dir, read));
727
+ for (const env of envs) environments.push(await readEnvironment(m, env, found.dir, read, revokedOf, query.readOutcomes));
513
728
  const own = await hasMemberLedger(m, found.dir);
514
729
  const gates = await readMemberGates(own ? `${MEMBERS_DIR}/${m.name}/${GATES_DIR}` : GATES_DIR, own ? "members" : "flat", commit, envs, found.dir, now, query.readGates, rules);
515
730
  const stewards = await (query.readStewards ?? readMemberStewards)(resolve(found.dir, m.dir), query.env, now, m.kind, m.box);
@@ -526,7 +741,7 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
526
741
  compare: query.compareTo === undefined ? null : compareEnvironments(environments[0], environments[environments.length - 1]),
527
742
  readable: environments.every((e) => e.reason === null),
528
743
  gateLedger: gates.ledger,
529
- gates: gates.gates,
744
+ gates: await supersedeRevokedGates(gates.gates, revokedOf),
530
745
  box:
531
746
  m.box === null
532
747
  ? null
@@ -553,6 +768,9 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
553
768
  },
554
769
  stewards: stewards.stewards,
555
770
  stewardReasons: stewards.reasons,
771
+ promotionOrder: await (query.readPromotionOrder ?? readMemberPromotionOrder)(resolve(found.dir, m.dir)),
772
+ releaseCuts: await readReleaseCuts(resolve(found.dir, m.dir), releaseRule),
773
+ featureFlags: flags === null ? null : flags.filter((f) => f.member === m.name).map(({ member: _member, ...f }) => f),
556
774
  ...x(m.pointer),
557
775
  });
558
776
  }
@@ -565,6 +783,16 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
565
783
 
566
784
  const leases = await readWorkspaceLeases(declaration.members, found.dir, new Date(now));
567
785
  const acceptance = await readWorkspaceAcceptance(declaration, found.dir);
786
+ // #4025: the change sets still waiting to apply, the ones a pending or approved gate binds.
787
+ const pendingChangeSets: OverlapChangeSetRef[] = [];
788
+ for (const m of members) {
789
+ const own = m.gateLedger.layout === "members";
790
+ for (const g of m.gates) {
791
+ if (g.changeSet === undefined || (g.state !== "pending" && g.state !== "approved")) continue;
792
+ pendingChangeSets.push({ digest: g.changeSet, cwd: resolve(found.dir, m.dir), ledgerMember: own ? m.name : null, gitSha: g.gitSha ?? null });
793
+ }
794
+ }
795
+ const overlap = await readStatusOverlap(declaration, found.dir, top, pendingChangeSets);
568
796
 
569
797
  return {
570
798
  ...head,
@@ -578,6 +806,7 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
578
806
  leases,
579
807
  acceptance,
580
808
  replication: replicationOf(declaration, top),
809
+ overlap,
581
810
  summary: {
582
811
  members: members.length,
583
812
  released: members.filter((m) => m.environments[0].releases.length > 0).length,
@@ -595,6 +824,35 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
595
824
  }
596
825
  }
597
826
 
827
+ /**
828
+ * A member's release cuts with their signatures judged (#4031), read from
829
+ * its ledger on the local `chant/lifecycle`. Null when they can't be read.
830
+ */
831
+ async function readReleaseCuts(memberDir: string, rule: GateAdmissionRule): Promise<StatusReleaseCut[] | null> {
832
+ try {
833
+ const { readCuts, readRelease } = await import("../lifecycle/release-store");
834
+ const loc = { cwd: memberDir };
835
+ const { cuts } = await readCuts(loc);
836
+ const commits = new Map<string, string | null>();
837
+ const out: StatusReleaseCut[] = [];
838
+ for (const c of cuts) {
839
+ if (!commits.has(c.releaseDigest)) commits.set(c.releaseDigest, (await readRelease(c.releaseDigest, loc))?.commit ?? null);
840
+ out.push({
841
+ releaseDigest: c.releaseDigest,
842
+ label: c.label,
843
+ commit: commits.get(c.releaseDigest) ?? null,
844
+ actor: c.actor,
845
+ timestamp: c.timestamp,
846
+ signature: c.signature,
847
+ signatureCheck: checkReleaseSignature(rule, c),
848
+ });
849
+ }
850
+ return out;
851
+ } catch {
852
+ return null;
853
+ }
854
+ }
855
+
598
856
  /**
599
857
  * The work leases of the flat ledger and of each member's (#2732). Leases are
600
858
  * env-independent, so the environment asked for doesn't narrow them. Never
@@ -610,6 +868,31 @@ async function readWorkspaceLeases(members: Member[], cwd: string, now: Date): P
610
868
  return out;
611
869
  }
612
870
 
871
+ /** Every flag of the declared flag kinds (#4024), read in the working tree, sorted by id; null when they can't be read. */
872
+ async function readStatusFlags(declaration: Declaration, root: string): Promise<Array<StatusFeatureFlag & { member: string }> | null> {
873
+ if (declaredRecordKinds(declaration).length === 0) return [];
874
+ try {
875
+ const { readWorkspaceFlags } = await import("./flags-read");
876
+ const { flags } = await readWorkspaceFlags(root);
877
+ return flags
878
+ .map((f) => ({ id: f.id, member: f.member, param: f.param, owner: f.owner, values: f.values, work: f.work, implements: f.implements, stale: f.stale, kind: f.kind, path: f.path }))
879
+ .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
880
+ } catch {
881
+ return null;
882
+ }
883
+ }
884
+
885
+ /** The overlap report (#4025); null when no work kind is declared or it can't be read. */
886
+ async function readStatusOverlap(declaration: Declaration, root: string, top: string, changeSets: OverlapChangeSetRef[]): Promise<OverlapReport | null> {
887
+ if (declaredRecordKinds(declaration).length === 0) return null;
888
+ try {
889
+ const { readOverlap } = await import("./overlap");
890
+ return await readOverlap({ declaration, root, top, changeSets });
891
+ } catch {
892
+ return null;
893
+ }
894
+ }
895
+
613
896
  /** The acceptance counts of every declared work kind with criteria (#2772), read in the working tree. */
614
897
  async function readWorkspaceAcceptance(declaration: Declaration, root: string): Promise<StatusAcceptance[]> {
615
898
  if (declaredRecordKinds(declaration).length === 0) return [];
@@ -695,6 +978,22 @@ export function formatStatus(doc: Extract<StatusDocument, { members: unknown }>)
695
978
  for (const e of m.environments) {
696
979
  if (e.reason) memberNotes.push(` ${e.reason.code}: ${e.reason.message}`);
697
980
  else if (e.ledger.shared && e.releases.length > 0) memberNotes.push(` ${e.env} is read from the shared flat ledger ${e.ledger.path}`);
981
+ // #4033: a component running a release cut from a revoked commit, and the way back.
982
+ for (const r of e.releases) {
983
+ if (!r.revoked) continue;
984
+ const name = r.revoked.label ?? r.revoked.releaseDigest;
985
+ memberNotes.push(` ${e.env}: ${r.component} runs release ${name}, revoked by ${r.revoked.tag} (${describeRevocation(r.revoked)}); roll back: ${r.revoked.rollback}`);
986
+ }
987
+ // #4035: an environment running a mix, what each component runs, and the lines that settle it.
988
+ if (e.mixed) {
989
+ const what = e.mixed.components.map((c) => {
990
+ const runs = c.releaseDigest ? `release ${c.releaseLabel ?? c.releaseDigest.slice(0, 24)}` : c.digest ? "no release" : "nothing recorded";
991
+ return c.state === "settled" ? `${c.component} runs ${runs}` : `${c.component} ${c.state} (${c.attempt!.verb} ${c.attempt!.runId}), recorded ${runs}`;
992
+ });
993
+ memberNotes.push(` ${e.env} is mixed: ${what.join("; ")}`);
994
+ for (const line of e.mixed.redeploy) memberNotes.push(` redeploy: ${line}`);
995
+ if (e.mixed.rollback) memberNotes.push(` roll back: ${e.mixed.rollback}`);
996
+ }
698
997
  }
699
998
  if (memberNotes.length > 0) notes.set(rows.length - 1, memberNotes);
700
999
  }
@@ -715,6 +1014,37 @@ export function formatStatus(doc: Extract<StatusDocument, { members: unknown }>)
715
1014
  for (const a of doc.acceptance) rows.push([a.member ? `${a.member}/${a.item}` : a.item, a.state ?? "-", `${a.met}/${a.total}`]);
716
1015
  lines.push(...table(rows));
717
1016
  }
1017
+ const cut = doc.members.filter((m) => (m.releaseCuts ?? []).length > 0);
1018
+ if (cut.length > 0) {
1019
+ lines.push("");
1020
+ const rows: string[][] = [["MEMBER", "RELEASE", "COMMIT", "CUT BY", "SIGNATURE"]];
1021
+ for (const m of cut) {
1022
+ for (const c of m.releaseCuts!) rows.push([m.name, c.label ?? c.releaseDigest.slice(0, 24), c.commit?.slice(0, 12) ?? "-", c.actor, c.signatureCheck.status]);
1023
+ }
1024
+ lines.push(...table(rows));
1025
+ }
1026
+ const flagged = doc.members.filter((m) => (m.featureFlags ?? []).length > 0);
1027
+ if (flagged.length > 0) {
1028
+ lines.push("");
1029
+ const rows: string[][] = [["FLAG", "PARAM", doc.env.toUpperCase(), "WORK ITEM", ""]];
1030
+ for (const m of flagged) {
1031
+ for (const f of m.featureFlags!) {
1032
+ const value = f.values[doc.env];
1033
+ rows.push([`${m.name}/${f.id}`, f.param, value === undefined ? "default" : JSON.stringify(value), f.work ? `${f.work.id} (${f.work.state ?? "unknown"})` : "-", f.stale ? "due for removal" : ""]);
1034
+ }
1035
+ }
1036
+ lines.push(...table(rows));
1037
+ }
1038
+ // #4025: in-flight work items that changed the same thing.
1039
+ if (doc.overlap && doc.overlap.pairs.length > 0) {
1040
+ lines.push("");
1041
+ const rows: string[][] = [["OVERLAP", "FILES", "ADDRESSES"]];
1042
+ for (const p of doc.overlap.pairs) {
1043
+ const list = (xs: string[]) => (xs.length === 0 ? "-" : xs.length <= 2 ? xs.join(", ") : `${xs.slice(0, 2).join(", ")} and ${xs.length - 2} more`);
1044
+ rows.push([`${p.items[0]} and ${p.items[1]}`, list(p.files), list([...new Set(p.addresses.map((x) => x.node ?? x.address))])]);
1045
+ }
1046
+ lines.push(...table(rows));
1047
+ }
718
1048
  const boxes = doc.members.filter((m) => m.box?.isolation);
719
1049
  if (boxes.length > 0) {
720
1050
  lines.push("");
@@ -0,0 +1,151 @@
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
+
30
+ import { z } from "zod";
31
+ import { canonicalJson } from "../../effect-receipt";
32
+ import { IN_TOTO_PAYLOAD_TYPE, signEnvelope, verifyEnvelope, type DsseEnvelope } from "./dsse";
33
+ import { STATEMENT_TYPE } from "./evidence";
34
+ import type { KeyObject } from "node:crypto";
35
+ import type { RunnerKey } from "./policy";
36
+
37
+ export const CI_TAG_PREDICATE = "https://intentius.io/chant/ci-tag/v1";
38
+
39
+ /** The role a tag's signer must hold in `.chant/trust.json` (#4031): the core class `runner`. */
40
+ export const CI_TAG_SIGNER_ROLE = "runner";
41
+
42
+ const fullCommit = z.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/);
43
+ const text = z.string().min(1);
44
+
45
+ /** The statement a ci tag carries. Strict at the top, so a verifier never acts on a field it does not know. */
46
+ export const ciTagStatementSchema = z
47
+ .object({
48
+ _type: z.literal(STATEMENT_TYPE),
49
+ subject: z
50
+ .array(
51
+ z
52
+ .object({ name: fullCommit, digest: z.object({ gitCommit: fullCommit }).strict() })
53
+ .strict()
54
+ .refine((s) => s.name === s.digest.gitCommit, "a commit subject is named by its own id"),
55
+ )
56
+ .length(1),
57
+ predicateType: z.literal(CI_TAG_PREDICATE),
58
+ predicate: z
59
+ .object({
60
+ signer: text,
61
+ record: z
62
+ .object({ schema: z.literal(1), tag: z.enum(["green", "revoked"]), commit: fullCommit, branch: text, at: text })
63
+ .passthrough(),
64
+ })
65
+ .strict(),
66
+ })
67
+ .strict();
68
+ export type CiTagStatement = z.infer<typeof ciTagStatementSchema>;
69
+
70
+ /** A tag's JSON record, without the statement. */
71
+ export type CiTagRecord = Record<string, unknown> & { tag: "green" | "revoked"; commit: string; branch: string };
72
+
73
+ /** Whoever signs the tags a tick makes: a runner key and the principal `.chant/trust.json` lists it for. */
74
+ export interface CiTagSigner {
75
+ principal: string;
76
+ key: KeyObject;
77
+ publicKey: string;
78
+ }
79
+
80
+ export function buildCiTagStatement(record: CiTagRecord, signer: string): CiTagStatement {
81
+ return {
82
+ _type: STATEMENT_TYPE,
83
+ subject: [{ name: record.commit, digest: { gitCommit: record.commit } }],
84
+ predicateType: CI_TAG_PREDICATE,
85
+ predicate: { signer, record: record as CiTagStatement["predicate"]["record"] },
86
+ };
87
+ }
88
+
89
+ /** The envelope a tag's record carries as `statement`, signed by `signer`. */
90
+ export function signCiTagRecord(record: CiTagRecord, signer: CiTagSigner): DsseEnvelope {
91
+ const statement = buildCiTagStatement(record, signer.principal);
92
+ return signEnvelope(IN_TOTO_PAYLOAD_TYPE, Buffer.from(canonicalJson(statement), "utf8"), signer.key, signer.publicKey);
93
+ }
94
+
95
+ /**
96
+ * Why a tag is ignored: it has no statement, the statement is not one, no
97
+ * runner key at base signed it, or it signs another tag's facts. The same
98
+ * words a run's attestation uses (`./run-statement.ts`).
99
+ */
100
+ export type CiTagFailure = "unsigned" | "invalid" | "untrusted" | "mismatch";
101
+
102
+ export type CiTagVerdict =
103
+ | { status: "signed"; signer: string; keyid: string; reason: string }
104
+ | { status: CiTagFailure; reason: string };
105
+
106
+ const normalise = (name: string): string => name.normalize("NFKC").trim().toLowerCase();
107
+
108
+ /**
109
+ * Verify the statement in a tag's record. `expect` is what the tag itself
110
+ * says: the commit its name gives, its kind and the declared branch. The
111
+ * signer's key must be a runner key at base and its principal must hold the
112
+ * `runner` role in `roles`. Never throws.
113
+ */
114
+ export function verifyCiTagStatement(
115
+ record: Record<string, unknown> | null,
116
+ expect: { sha: string; kind: "green" | "revoked"; branch: string },
117
+ runners: readonly RunnerKey[],
118
+ roles: Readonly<Record<string, readonly string[]>>,
119
+ ): CiTagVerdict {
120
+ const tag = `ci/${expect.kind}/${expect.sha}`;
121
+ if (record === null) 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)` };
122
+ const { statement: envelope, ...fields } = record;
123
+ if (envelope === undefined) return { status: "unsigned", reason: `${tag} carries no statement signed by a runner key` };
124
+ const v = verifyEnvelope(envelope, runners);
125
+ if (!v.ok) return { status: v.code === "envelope-untrusted" ? "untrusted" : "invalid", reason: `${tag}: ${v.reason}` };
126
+ if (!(roles[CI_TAG_SIGNER_ROLE] ?? []).some((p) => normalise(p) === normalise(v.principal))) {
127
+ 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` };
128
+ }
129
+ if (v.payloadType !== IN_TOTO_PAYLOAD_TYPE) return { status: "invalid", reason: `${tag}: payload type ${v.payloadType} is not ${IN_TOTO_PAYLOAD_TYPE}` };
130
+ let raw: unknown;
131
+ try {
132
+ raw = JSON.parse(v.payload.toString("utf8"));
133
+ } catch {
134
+ return { status: "invalid", reason: `${tag}: the statement's payload is not JSON` };
135
+ }
136
+ const parsed = ciTagStatementSchema.safeParse(raw);
137
+ if (!parsed.success) {
138
+ const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "/"}: ${i.message}`).join("; ");
139
+ return { status: "invalid", reason: `${tag}: the payload is not a ${CI_TAG_PREDICATE} statement: ${detail}` };
140
+ }
141
+ const p = parsed.data.predicate;
142
+ if (p.signer !== v.principal) return { status: "invalid", reason: `${tag}: the statement names signer ${JSON.stringify(p.signer)}, but ${v.principal}'s key signed it` };
143
+ const differs: string[] = [];
144
+ if (parsed.data.subject[0]!.name !== expect.sha) differs.push(`it is for commit ${parsed.data.subject[0]!.name.slice(0, 12)}`);
145
+ if (p.record.commit !== expect.sha) differs.push(`its record names commit ${p.record.commit.slice(0, 12)}`);
146
+ if (p.record.tag !== expect.kind) differs.push(`it signs a ${p.record.tag} tag`);
147
+ if (p.record.branch !== expect.branch) differs.push(`it is for branch ${p.record.branch}, and ci.green names ${expect.branch}`);
148
+ if (canonicalJson(p.record) !== canonicalJson(fields)) differs.push("the annotation's record is not the one signed");
149
+ if (differs.length > 0) return { status: "mismatch", reason: `${tag} is signed by ${v.principal}, but the statement does not match the tag: ${differs.join("; ")}` };
150
+ return { status: "signed", signer: v.principal, keyid: v.keyid, reason: `signed by ${v.principal} (${CI_TAG_SIGNER_ROLE})` };
151
+ }