@intentius/chant 0.124.27 → 0.124.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (681) hide show
  1. package/README.md +1 -1
  2. package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
  3. package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
  4. package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  6. package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
  7. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  8. package/dist/__golden__/wave-loop/op-held-on.json +2 -0
  9. package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
  10. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  11. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  12. package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
  13. package/dist/__golden__/wave-loop/op-per-run.json +7 -0
  14. package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
  15. package/dist/__golden__/wave-loop/op-sealed.json +3 -0
  16. package/dist/__golden__/wave-loop/op-spend.json +3 -0
  17. package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
  18. package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
  19. package/dist/build-params.d.ts +34 -1
  20. package/dist/build-params.d.ts.map +1 -1
  21. package/dist/build-params.js +31 -1
  22. package/dist/change-set-graph.d.ts +63 -0
  23. package/dist/change-set-graph.d.ts.map +1 -0
  24. package/dist/change-set-graph.js +130 -0
  25. package/dist/change-set.d.ts +62 -3
  26. package/dist/change-set.d.ts.map +1 -1
  27. package/dist/change-set.js +36 -4
  28. package/dist/chant-version.d.ts +9 -0
  29. package/dist/chant-version.d.ts.map +1 -0
  30. package/dist/chant-version.js +16 -0
  31. package/dist/cli/build-params-cli.d.ts +7 -1
  32. package/dist/cli/build-params-cli.d.ts.map +1 -1
  33. package/dist/cli/build-params-cli.js +17 -8
  34. package/dist/cli/commands/build.d.ts +8 -0
  35. package/dist/cli/commands/build.d.ts.map +1 -1
  36. package/dist/cli/commands/build.js +15 -11
  37. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  38. package/dist/cli/commands/check-lexicon-docs.js +30 -2
  39. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  40. package/dist/cli/commands/check-lexicon.js +13 -2
  41. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
  42. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  43. package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
  44. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  45. package/dist/cli/commands/init-lexicon.js +17 -8
  46. package/dist/cli/handlers/approve.d.ts.map +1 -1
  47. package/dist/cli/handlers/approve.js +64 -4
  48. package/dist/cli/handlers/build.d.ts.map +1 -1
  49. package/dist/cli/handlers/build.js +43 -4
  50. package/dist/cli/handlers/change-set.d.ts +10 -0
  51. package/dist/cli/handlers/change-set.d.ts.map +1 -1
  52. package/dist/cli/handlers/change-set.js +51 -1
  53. package/dist/cli/handlers/fan-out.d.ts +10 -1
  54. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  55. package/dist/cli/handlers/fan-out.js +54 -6
  56. package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
  57. package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
  58. package/dist/cli/handlers/lifecycle-gc.js +143 -0
  59. package/dist/cli/handlers/pr.d.ts +16 -2
  60. package/dist/cli/handlers/pr.d.ts.map +1 -1
  61. package/dist/cli/handlers/pr.js +269 -37
  62. package/dist/cli/handlers/promote.d.ts +7 -0
  63. package/dist/cli/handlers/promote.d.ts.map +1 -1
  64. package/dist/cli/handlers/promote.js +440 -28
  65. package/dist/cli/handlers/release.d.ts +91 -0
  66. package/dist/cli/handlers/release.d.ts.map +1 -0
  67. package/dist/cli/handlers/release.js +589 -0
  68. package/dist/cli/handlers/run.d.ts +38 -2
  69. package/dist/cli/handlers/run.d.ts.map +1 -1
  70. package/dist/cli/handlers/run.js +234 -16
  71. package/dist/cli/main.d.ts.map +1 -1
  72. package/dist/cli/main.js +143 -17
  73. package/dist/cli/mcp/workspace-tools.js +1 -1
  74. package/dist/cli/parse-args.d.ts.map +1 -1
  75. package/dist/cli/parse-args.js +73 -5
  76. package/dist/cli/registry.d.ts +43 -5
  77. package/dist/cli/registry.d.ts.map +1 -1
  78. package/dist/cli/run-events.d.ts +30 -0
  79. package/dist/cli/run-events.d.ts.map +1 -0
  80. package/dist/cli/run-events.js +49 -0
  81. package/dist/cli/version.d.ts +3 -5
  82. package/dist/cli/version.d.ts.map +1 -1
  83. package/dist/cli/version.js +3 -13
  84. package/dist/codegen/docs-in-site.d.ts +71 -0
  85. package/dist/codegen/docs-in-site.d.ts.map +1 -0
  86. package/dist/codegen/docs-in-site.js +177 -0
  87. package/dist/codegen/docs-types.d.ts +14 -1
  88. package/dist/codegen/docs-types.d.ts.map +1 -1
  89. package/dist/codegen/docs.d.ts +18 -1
  90. package/dist/codegen/docs.d.ts.map +1 -1
  91. package/dist/codegen/docs.js +40 -7
  92. package/dist/codegen/rehype-base-url.mjs +15 -9
  93. package/dist/components/auto-release.d.ts +6 -1
  94. package/dist/components/auto-release.d.ts.map +1 -1
  95. package/dist/components/auto-release.js +3 -0
  96. package/dist/components/capability.d.ts +9 -0
  97. package/dist/components/capability.d.ts.map +1 -1
  98. package/dist/components/cli-support.d.ts +6 -0
  99. package/dist/components/cli-support.d.ts.map +1 -1
  100. package/dist/components/cli-support.js +10 -9
  101. package/dist/components/driver.d.ts +50 -9
  102. package/dist/components/driver.d.ts.map +1 -1
  103. package/dist/components/driver.js +116 -54
  104. package/dist/components/fan-out-inputs.d.ts +69 -0
  105. package/dist/components/fan-out-inputs.d.ts.map +1 -0
  106. package/dist/components/fan-out-inputs.js +169 -0
  107. package/dist/components/fan-out-record.d.ts +13 -0
  108. package/dist/components/fan-out-record.d.ts.map +1 -1
  109. package/dist/components/fan-out-record.js +16 -0
  110. package/dist/components/fan-out-run.d.ts +7 -0
  111. package/dist/components/fan-out-run.d.ts.map +1 -1
  112. package/dist/components/fan-out-run.js +85 -26
  113. package/dist/components/fan-out.d.ts +4 -0
  114. package/dist/components/fan-out.d.ts.map +1 -1
  115. package/dist/components/pr-pipeline.d.ts +25 -0
  116. package/dist/components/pr-pipeline.d.ts.map +1 -1
  117. package/dist/components/pr-pipeline.js +22 -4
  118. package/dist/components/pr-run.d.ts +11 -0
  119. package/dist/components/pr-run.d.ts.map +1 -1
  120. package/dist/components/pr-run.js +20 -1
  121. package/dist/components/promote-order.d.ts +38 -0
  122. package/dist/components/promote-order.d.ts.map +1 -0
  123. package/dist/components/promote-order.js +62 -0
  124. package/dist/components/promote.d.ts +38 -2
  125. package/dist/components/promote.d.ts.map +1 -1
  126. package/dist/components/promote.js +77 -2
  127. package/dist/components/release-compile.d.ts +142 -0
  128. package/dist/components/release-compile.d.ts.map +1 -0
  129. package/dist/components/release-compile.js +382 -0
  130. package/dist/components/release-gate.d.ts +135 -0
  131. package/dist/components/release-gate.d.ts.map +1 -0
  132. package/dist/components/release-gate.js +288 -0
  133. package/dist/components/release-promote.d.ts +96 -0
  134. package/dist/components/release-promote.d.ts.map +1 -0
  135. package/dist/components/release-promote.js +204 -0
  136. package/dist/components/run-progress.d.ts +7 -0
  137. package/dist/components/run-progress.d.ts.map +1 -1
  138. package/dist/components/wave-plan.d.ts +8 -1
  139. package/dist/components/wave-plan.d.ts.map +1 -1
  140. package/dist/components/wave-plan.js +11 -3
  141. package/dist/config.d.ts +45 -0
  142. package/dist/config.d.ts.map +1 -1
  143. package/dist/config.js +3 -0
  144. package/dist/flag-params.d.ts +54 -0
  145. package/dist/flag-params.d.ts.map +1 -0
  146. package/dist/flag-params.js +130 -0
  147. package/dist/index.d.ts +1 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +2 -0
  150. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  151. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  152. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  153. package/dist/lifecycle/change-set-store.d.ts +68 -0
  154. package/dist/lifecycle/change-set-store.d.ts.map +1 -0
  155. package/dist/lifecycle/change-set-store.js +97 -0
  156. package/dist/lifecycle/commit-sha.d.ts +13 -0
  157. package/dist/lifecycle/commit-sha.d.ts.map +1 -0
  158. package/dist/lifecycle/commit-sha.js +21 -0
  159. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  160. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  161. package/dist/lifecycle/delivery-metrics.js +145 -0
  162. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  163. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  164. package/dist/lifecycle/deploy-outcome.js +101 -0
  165. package/dist/lifecycle/gate-change-set.d.ts +78 -0
  166. package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
  167. package/dist/lifecycle/gate-change-set.js +138 -0
  168. package/dist/lifecycle/gate-ledger.d.ts +51 -4
  169. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  170. package/dist/lifecycle/gate-ledger.js +18 -0
  171. package/dist/lifecycle/git.d.ts +30 -1
  172. package/dist/lifecycle/git.d.ts.map +1 -1
  173. package/dist/lifecycle/git.js +31 -5
  174. package/dist/lifecycle/index.d.ts +4 -0
  175. package/dist/lifecycle/index.d.ts.map +1 -1
  176. package/dist/lifecycle/index.js +4 -0
  177. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  178. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  179. package/dist/lifecycle/mixed-environment.js +154 -0
  180. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  181. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  182. package/dist/lifecycle/publish-attachments.js +179 -0
  183. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  184. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  185. package/dist/lifecycle/publish-credentials.js +69 -0
  186. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  187. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  188. package/dist/lifecycle/publish-declaration.js +83 -0
  189. package/dist/lifecycle/publish-forge.d.ts +53 -0
  190. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  191. package/dist/lifecycle/publish-forge.js +298 -0
  192. package/dist/lifecycle/publish-npm.d.ts +61 -0
  193. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  194. package/dist/lifecycle/publish-npm.js +242 -0
  195. package/dist/lifecycle/publish-oci.d.ts +64 -0
  196. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  197. package/dist/lifecycle/publish-oci.js +383 -0
  198. package/dist/lifecycle/publish-record.d.ts +66 -0
  199. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  200. package/dist/lifecycle/publish-record.js +84 -0
  201. package/dist/lifecycle/publish-targets.d.ts +45 -0
  202. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  203. package/dist/lifecycle/publish-targets.js +85 -0
  204. package/dist/lifecycle/release-cut.d.ts +154 -0
  205. package/dist/lifecycle/release-cut.d.ts.map +1 -0
  206. package/dist/lifecycle/release-cut.js +366 -0
  207. package/dist/lifecycle/release-join.d.ts +95 -0
  208. package/dist/lifecycle/release-join.d.ts.map +1 -0
  209. package/dist/lifecycle/release-join.js +139 -0
  210. package/dist/lifecycle/release-ledger.d.ts +54 -0
  211. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  212. package/dist/lifecycle/release-ledger.js +53 -20
  213. package/dist/lifecycle/release-publish.d.ts +312 -0
  214. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  215. package/dist/lifecycle/release-publish.js +335 -0
  216. package/dist/lifecycle/release-revocation.d.ts +80 -0
  217. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  218. package/dist/lifecycle/release-revocation.js +123 -0
  219. package/dist/lifecycle/release-store.d.ts +200 -0
  220. package/dist/lifecycle/release-store.d.ts.map +1 -0
  221. package/dist/lifecycle/release-store.js +320 -0
  222. package/dist/lifecycle/release-verify.d.ts +61 -0
  223. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  224. package/dist/lifecycle/release-verify.js +92 -0
  225. package/dist/lifecycle/release-workstation.d.ts +91 -0
  226. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  227. package/dist/lifecycle/release-workstation.js +180 -0
  228. package/dist/lifecycle/retention-policy.d.ts +45 -0
  229. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  230. package/dist/lifecycle/retention-policy.js +43 -0
  231. package/dist/lifecycle/retention.d.ts +185 -0
  232. package/dist/lifecycle/retention.d.ts.map +1 -0
  233. package/dist/lifecycle/retention.js +400 -0
  234. package/dist/lifecycle/run-ledger.d.ts +4 -0
  235. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  236. package/dist/lifecycle/run-ledger.js +2 -0
  237. package/dist/op/activities/change-coverage.d.ts.map +1 -1
  238. package/dist/op/activities/change-coverage.js +4 -2
  239. package/dist/op/activities/source-release.d.ts.map +1 -1
  240. package/dist/op/activities/source-release.js +7 -3
  241. package/dist/op/activities/source-rollback.d.ts.map +1 -1
  242. package/dist/op/activities/source-rollback.js +7 -3
  243. package/dist/op/gate-name.d.ts +11 -2
  244. package/dist/op/gate-name.d.ts.map +1 -1
  245. package/dist/op/gate-name.js +12 -3
  246. package/dist/op/gate-run-locator.d.ts +22 -0
  247. package/dist/op/gate-run-locator.d.ts.map +1 -1
  248. package/dist/op/gate-run-locator.js +31 -0
  249. package/dist/op/gate.d.ts +27 -3
  250. package/dist/op/gate.d.ts.map +1 -1
  251. package/dist/op/gate.js +57 -3
  252. package/dist/op/local-executor.d.ts +36 -1
  253. package/dist/op/local-executor.d.ts.map +1 -1
  254. package/dist/op/local-executor.js +158 -52
  255. package/dist/op/op-waves-run.d.ts.map +1 -1
  256. package/dist/op/op-waves-run.js +4 -0
  257. package/dist/op/run-live.d.ts +2 -0
  258. package/dist/op/run-live.d.ts.map +1 -1
  259. package/dist/op/run-live.js +1 -1
  260. package/dist/op/runtime.d.ts +46 -0
  261. package/dist/op/runtime.d.ts.map +1 -1
  262. package/dist/op/runtimes/local.d.ts.map +1 -1
  263. package/dist/op/runtimes/local.js +8 -0
  264. package/dist/plan-summary.d.ts.map +1 -1
  265. package/dist/plan-summary.js +8 -2
  266. package/dist/point-decision.d.ts +2 -0
  267. package/dist/point-decision.d.ts.map +1 -1
  268. package/dist/point-decision.js +9 -2
  269. package/dist/pr-loop.d.ts +7 -0
  270. package/dist/pr-loop.d.ts.map +1 -1
  271. package/dist/pr-loop.js +23 -4
  272. package/dist/pr-report-url.d.ts +82 -0
  273. package/dist/pr-report-url.d.ts.map +1 -0
  274. package/dist/pr-report-url.js +174 -0
  275. package/dist/provenance.d.ts +2 -2
  276. package/dist/provenance.d.ts.map +1 -1
  277. package/dist/run-event-store.d.ts +104 -0
  278. package/dist/run-event-store.d.ts.map +1 -0
  279. package/dist/run-event-store.js +251 -0
  280. package/dist/run-event-stream.d.ts +133 -0
  281. package/dist/run-event-stream.d.ts.map +1 -0
  282. package/dist/run-event-stream.js +242 -0
  283. package/dist/run-event.d.ts +285 -0
  284. package/dist/run-event.d.ts.map +1 -0
  285. package/dist/run-event.js +209 -0
  286. package/dist/wave-runtime.mjs +262 -226
  287. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  288. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  289. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  290. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  291. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  292. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  293. package/dist/workspace/change-set-show.d.ts +56 -0
  294. package/dist/workspace/change-set-show.d.ts.map +1 -0
  295. package/dist/workspace/change-set-show.js +54 -0
  296. package/dist/workspace/change-set-show.schema.json +102 -0
  297. package/dist/workspace/change-set.schema.json +32 -1
  298. package/dist/workspace/changes-cli.d.ts.map +1 -1
  299. package/dist/workspace/changes-cli.js +10 -1
  300. package/dist/workspace/changes.d.ts +37 -0
  301. package/dist/workspace/changes.d.ts.map +1 -1
  302. package/dist/workspace/changes.js +49 -2
  303. package/dist/workspace/changes.schema.json +135 -0
  304. package/dist/workspace/check.schema.json +4 -2
  305. package/dist/workspace/checks/records.d.ts +9 -1
  306. package/dist/workspace/checks/records.d.ts.map +1 -1
  307. package/dist/workspace/checks/records.js +35 -1
  308. package/dist/workspace/checks.d.ts +1 -0
  309. package/dist/workspace/checks.d.ts.map +1 -1
  310. package/dist/workspace/checks.js +2 -1
  311. package/dist/workspace/ci-green-cli.d.ts +19 -4
  312. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  313. package/dist/workspace/ci-green-cli.js +75 -12
  314. package/dist/workspace/ci-green-forge.d.ts +20 -3
  315. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  316. package/dist/workspace/ci-green-forge.js +6 -4
  317. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  318. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  319. package/dist/workspace/ci-green-workflow.js +6 -0
  320. package/dist/workspace/ci-green.d.ts +281 -13
  321. package/dist/workspace/ci-green.d.ts.map +1 -1
  322. package/dist/workspace/ci-green.js +456 -38
  323. package/dist/workspace/ci-last-green.schema.json +32 -2
  324. package/dist/workspace/ci-list.schema.json +525 -0
  325. package/dist/workspace/commit-links.d.ts +85 -0
  326. package/dist/workspace/commit-links.d.ts.map +1 -0
  327. package/dist/workspace/commit-links.js +163 -0
  328. package/dist/workspace/declaration.d.ts +35 -0
  329. package/dist/workspace/declaration.d.ts.map +1 -1
  330. package/dist/workspace/declaration.js +27 -1
  331. package/dist/workspace/declaration.schema.json +76 -0
  332. package/dist/workspace/declared-kinds.d.ts +16 -0
  333. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  334. package/dist/workspace/declared-kinds.js +25 -0
  335. package/dist/workspace/delivery-metrics.d.ts +91 -0
  336. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  337. package/dist/workspace/delivery-metrics.js +217 -0
  338. package/dist/workspace/delivery-metrics.schema.json +519 -0
  339. package/dist/workspace/flags-read.d.ts +37 -0
  340. package/dist/workspace/flags-read.d.ts.map +1 -0
  341. package/dist/workspace/flags-read.js +103 -0
  342. package/dist/workspace/flags.d.ts +100 -0
  343. package/dist/workspace/flags.d.ts.map +1 -0
  344. package/dist/workspace/flags.js +98 -0
  345. package/dist/workspace/graph-cli.js +1 -1
  346. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  347. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  348. package/dist/workspace/identity-gate-rule.js +72 -7
  349. package/dist/workspace/identity.d.ts +12 -0
  350. package/dist/workspace/identity.d.ts.map +1 -1
  351. package/dist/workspace/identity.js +13 -1
  352. package/dist/workspace/intent-cli.d.ts +11 -2
  353. package/dist/workspace/intent-cli.d.ts.map +1 -1
  354. package/dist/workspace/intent-cli.js +54 -5
  355. package/dist/workspace/intent-trace.d.ts +219 -0
  356. package/dist/workspace/intent-trace.d.ts.map +1 -0
  357. package/dist/workspace/intent-trace.js +314 -0
  358. package/dist/workspace/intent.d.ts +36 -7
  359. package/dist/workspace/intent.d.ts.map +1 -1
  360. package/dist/workspace/intent.js +192 -22
  361. package/dist/workspace/intent.schema.json +702 -6
  362. package/dist/workspace/ls.d.ts +4 -0
  363. package/dist/workspace/ls.d.ts.map +1 -1
  364. package/dist/workspace/ls.js +2 -0
  365. package/dist/workspace/ls.schema.json +3 -1
  366. package/dist/workspace/member-pipeline.d.ts +6 -0
  367. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  368. package/dist/workspace/member-pipeline.js +6 -0
  369. package/dist/workspace/overlap.d.ts +146 -0
  370. package/dist/workspace/overlap.d.ts.map +1 -0
  371. package/dist/workspace/overlap.js +276 -0
  372. package/dist/workspace/promotion-order.d.ts +41 -0
  373. package/dist/workspace/promotion-order.d.ts.map +1 -0
  374. package/dist/workspace/promotion-order.js +95 -0
  375. package/dist/workspace/reason-codes.d.ts +4 -0
  376. package/dist/workspace/reason-codes.d.ts.map +1 -1
  377. package/dist/workspace/reason-codes.js +7 -1
  378. package/dist/workspace/records.d.ts +15 -2
  379. package/dist/workspace/records.d.ts.map +1 -1
  380. package/dist/workspace/records.js +32 -0
  381. package/dist/workspace/records.schema.json +21 -2
  382. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  383. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  384. package/dist/workspace/releases.d.ts +81 -0
  385. package/dist/workspace/releases.d.ts.map +1 -0
  386. package/dist/workspace/releases.js +103 -0
  387. package/dist/workspace/releases.schema.json +513 -0
  388. package/dist/workspace/run-event.schema.json +295 -0
  389. package/dist/workspace/status-gates.d.ts +34 -1
  390. package/dist/workspace/status-gates.d.ts.map +1 -1
  391. package/dist/workspace/status-gates.js +31 -2
  392. package/dist/workspace/status-stewards.d.ts +4 -0
  393. package/dist/workspace/status-stewards.d.ts.map +1 -1
  394. package/dist/workspace/status-stewards.js +2 -0
  395. package/dist/workspace/status.d.ts +117 -0
  396. package/dist/workspace/status.d.ts.map +1 -1
  397. package/dist/workspace/status.js +218 -4
  398. package/dist/workspace/status.schema.json +268 -2
  399. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  400. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  401. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  402. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  403. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  404. package/dist/workspace/trust/op-run-statement.js +143 -0
  405. package/dist/workspace/trust/rotation.d.ts +18 -2
  406. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  407. package/dist/workspace/trust/rotation.js +65 -16
  408. package/dist/workspace/trust/seal.d.ts +11 -0
  409. package/dist/workspace/trust/seal.d.ts.map +1 -1
  410. package/dist/workspace/trust/seal.js +9 -0
  411. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  412. package/dist/workspace/trust/ssh-commit.js +10 -10
  413. package/dist/workspace/wip.d.ts.map +1 -1
  414. package/dist/workspace/wip.js +11 -3
  415. package/package.json +1 -1
  416. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  417. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  418. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  419. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  420. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  421. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  422. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  423. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  424. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  425. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  426. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  427. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  428. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  429. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  430. package/src/__golden__/wave-loop/op-spend.json +3 -0
  431. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  432. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  433. package/src/build-params.test.ts +46 -0
  434. package/src/build-params.ts +55 -1
  435. package/src/change-set-graph.test.ts +72 -0
  436. package/src/change-set-graph.ts +141 -0
  437. package/src/change-set.ts +87 -6
  438. package/src/chant-version.ts +16 -0
  439. package/src/cli/build-params-cli.ts +22 -9
  440. package/src/cli/commands/build.ts +25 -11
  441. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  442. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  443. package/src/cli/commands/check-lexicon.ts +14 -2
  444. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  445. package/src/cli/commands/init-lexicon.test.ts +25 -0
  446. package/src/cli/commands/init-lexicon.ts +18 -8
  447. package/src/cli/handlers/approve-release.test.ts +84 -0
  448. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  449. package/src/cli/handlers/approve.ts +65 -2
  450. package/src/cli/handlers/build.test.ts +67 -0
  451. package/src/cli/handlers/build.ts +46 -4
  452. package/src/cli/handlers/change-set.ts +49 -1
  453. package/src/cli/handlers/fan-out.test.ts +228 -1
  454. package/src/cli/handlers/fan-out.ts +53 -6
  455. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  456. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  457. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  458. package/src/cli/handlers/operator.test.ts +37 -0
  459. package/src/cli/handlers/pr.test.ts +341 -0
  460. package/src/cli/handlers/pr.ts +292 -47
  461. package/src/cli/handlers/promote.test.ts +669 -7
  462. package/src/cli/handlers/promote.ts +464 -29
  463. package/src/cli/handlers/release-implicit.e2e.test.ts +161 -0
  464. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  465. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  466. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  467. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  468. package/src/cli/handlers/release.e2e.test.ts +199 -0
  469. package/src/cli/handlers/release.ts +593 -0
  470. package/src/cli/handlers/run-auto-release.test.ts +80 -0
  471. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  472. package/src/cli/handlers/run-release.test.ts +55 -0
  473. package/src/cli/handlers/run.test.ts +30 -0
  474. package/src/cli/handlers/run.ts +243 -18
  475. package/src/cli/main.test.ts +8 -0
  476. package/src/cli/main.ts +143 -17
  477. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  478. package/src/cli/mcp/workspace-tools.ts +1 -1
  479. package/src/cli/parse-args.ts +56 -5
  480. package/src/cli/registry.ts +43 -5
  481. package/src/cli/run-events.ts +57 -0
  482. package/src/cli/version.ts +3 -13
  483. package/src/codegen/docs-in-site.test.ts +166 -0
  484. package/src/codegen/docs-in-site.ts +182 -0
  485. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  486. package/src/codegen/docs-types.ts +14 -1
  487. package/src/codegen/docs.ts +60 -7
  488. package/src/codegen/rehype-base-url.d.mts +3 -1
  489. package/src/codegen/rehype-base-url.mjs +15 -9
  490. package/src/codegen/rehype-base-url.test.ts +34 -0
  491. package/src/components/auto-release.test.ts +30 -0
  492. package/src/components/auto-release.ts +9 -1
  493. package/src/components/capability.ts +9 -0
  494. package/src/components/cli-support.test.ts +82 -0
  495. package/src/components/cli-support.ts +16 -9
  496. package/src/components/driver.test.ts +24 -0
  497. package/src/components/driver.ts +165 -56
  498. package/src/components/fan-out-inputs.test.ts +85 -0
  499. package/src/components/fan-out-inputs.ts +192 -0
  500. package/src/components/fan-out-record.test.ts +10 -0
  501. package/src/components/fan-out-record.ts +19 -0
  502. package/src/components/fan-out-run.ts +104 -35
  503. package/src/components/fan-out.ts +4 -0
  504. package/src/components/pr-pipeline.ts +35 -4
  505. package/src/components/pr-run.ts +30 -1
  506. package/src/components/promote-order.test.ts +90 -0
  507. package/src/components/promote-order.ts +75 -0
  508. package/src/components/promote.test.ts +10 -1
  509. package/src/components/promote.ts +94 -3
  510. package/src/components/release-compile.test.ts +147 -0
  511. package/src/components/release-compile.ts +459 -0
  512. package/src/components/release-gate.test.ts +215 -0
  513. package/src/components/release-gate.ts +355 -0
  514. package/src/components/release-promote.test.ts +159 -0
  515. package/src/components/release-promote.ts +232 -0
  516. package/src/components/run-progress.ts +7 -0
  517. package/src/components/wave-plan.ts +16 -3
  518. package/src/config.ts +15 -1
  519. package/src/flag-params.test.ts +144 -0
  520. package/src/flag-params.ts +134 -0
  521. package/src/index.ts +2 -0
  522. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  523. package/src/lifecycle/change-set-store.test.ts +217 -0
  524. package/src/lifecycle/change-set-store.ts +113 -0
  525. package/src/lifecycle/commit-sha.ts +23 -0
  526. package/src/lifecycle/delivery-metrics.ts +263 -0
  527. package/src/lifecycle/deploy-outcome.ts +147 -0
  528. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  529. package/src/lifecycle/gate-change-set.ts +164 -0
  530. package/src/lifecycle/gate-ledger.test.ts +33 -0
  531. package/src/lifecycle/gate-ledger.ts +68 -3
  532. package/src/lifecycle/git.ts +61 -8
  533. package/src/lifecycle/index.ts +4 -0
  534. package/src/lifecycle/mixed-environment.test.ts +97 -0
  535. package/src/lifecycle/mixed-environment.ts +204 -0
  536. package/src/lifecycle/publish-attachments.ts +196 -0
  537. package/src/lifecycle/publish-credentials.ts +84 -0
  538. package/src/lifecycle/publish-declaration.ts +95 -0
  539. package/src/lifecycle/publish-forge.ts +325 -0
  540. package/src/lifecycle/publish-npm.ts +259 -0
  541. package/src/lifecycle/publish-oci.ts +396 -0
  542. package/src/lifecycle/publish-record.ts +113 -0
  543. package/src/lifecycle/publish-resume.test.ts +168 -0
  544. package/src/lifecycle/publish-targets.test.ts +494 -0
  545. package/src/lifecycle/publish-targets.ts +100 -0
  546. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  547. package/src/lifecycle/release-cut.test.ts +483 -0
  548. package/src/lifecycle/release-cut.ts +503 -0
  549. package/src/lifecycle/release-flag.test.ts +112 -0
  550. package/src/lifecycle/release-join.test.ts +68 -0
  551. package/src/lifecycle/release-join.ts +182 -0
  552. package/src/lifecycle/release-ledger.test.ts +14 -0
  553. package/src/lifecycle/release-ledger.ts +85 -17
  554. package/src/lifecycle/release-publish.test.ts +225 -0
  555. package/src/lifecycle/release-publish.ts +568 -0
  556. package/src/lifecycle/release-revocation.ts +147 -0
  557. package/src/lifecycle/release-store.ts +434 -0
  558. package/src/lifecycle/release-verify.test.ts +110 -0
  559. package/src/lifecycle/release-verify.ts +120 -0
  560. package/src/lifecycle/release-workstation.test.ts +204 -0
  561. package/src/lifecycle/release-workstation.ts +216 -0
  562. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  563. package/src/lifecycle/retention-policy.ts +63 -0
  564. package/src/lifecycle/retention.test.ts +363 -0
  565. package/src/lifecycle/retention.ts +508 -0
  566. package/src/lifecycle/run-ledger.test.ts +10 -0
  567. package/src/lifecycle/run-ledger.ts +6 -0
  568. package/src/op/activities/change-coverage.ts +4 -1
  569. package/src/op/activities/source-release.ts +8 -3
  570. package/src/op/activities/source-rollback.ts +8 -4
  571. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  572. package/src/op/gate-name.ts +13 -3
  573. package/src/op/gate-release.test.ts +126 -0
  574. package/src/op/gate-run-locator.test.ts +110 -0
  575. package/src/op/gate-run-locator.ts +35 -0
  576. package/src/op/gate.ts +88 -6
  577. package/src/op/local-executor.ts +207 -52
  578. package/src/op/op-waves-run.ts +4 -0
  579. package/src/op/run-events.test.ts +166 -0
  580. package/src/op/run-live.ts +1 -1
  581. package/src/op/runtime.ts +39 -0
  582. package/src/op/runtimes/local.test.ts +16 -0
  583. package/src/op/runtimes/local.ts +9 -0
  584. package/src/plan-summary.test.ts +20 -0
  585. package/src/plan-summary.ts +8 -1
  586. package/src/point-decision.ts +12 -2
  587. package/src/pr-loop.ts +25 -4
  588. package/src/pr-report-url.test.ts +131 -0
  589. package/src/pr-report-url.ts +209 -0
  590. package/src/provenance.ts +2 -2
  591. package/src/run-event-store.test.ts +144 -0
  592. package/src/run-event-store.ts +273 -0
  593. package/src/run-event-stream.test.ts +175 -0
  594. package/src/run-event-stream.ts +309 -0
  595. package/src/run-event.test.ts +277 -0
  596. package/src/run-event.ts +471 -0
  597. package/src/wave-loop-golden.test.ts +9 -0
  598. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  599. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  600. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  601. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  602. package/src/workspace/ancestry-index.test.ts +93 -0
  603. package/src/workspace/change-set-show.schema.json +102 -0
  604. package/src/workspace/change-set-show.ts +72 -0
  605. package/src/workspace/change-set.schema.json +32 -1
  606. package/src/workspace/changes-cli.ts +10 -1
  607. package/src/workspace/changes.schema.json +135 -0
  608. package/src/workspace/changes.test.ts +1 -1
  609. package/src/workspace/changes.ts +84 -2
  610. package/src/workspace/check.schema.json +4 -2
  611. package/src/workspace/checks/records.ts +35 -1
  612. package/src/workspace/checks.ts +2 -1
  613. package/src/workspace/ci-green-cli.ts +75 -13
  614. package/src/workspace/ci-green-forge.ts +24 -4
  615. package/src/workspace/ci-green-runs.test.ts +242 -0
  616. package/src/workspace/ci-green-signed.test.ts +302 -0
  617. package/src/workspace/ci-green-workflow.test.ts +16 -0
  618. package/src/workspace/ci-green-workflow.ts +12 -0
  619. package/src/workspace/ci-green.test.ts +131 -2
  620. package/src/workspace/ci-green.ts +635 -42
  621. package/src/workspace/ci-last-green.schema.json +32 -2
  622. package/src/workspace/ci-list.schema.json +525 -0
  623. package/src/workspace/commit-links.test.ts +279 -0
  624. package/src/workspace/commit-links.ts +183 -0
  625. package/src/workspace/declaration.schema.json +76 -0
  626. package/src/workspace/declaration.test.ts +28 -0
  627. package/src/workspace/declaration.ts +60 -2
  628. package/src/workspace/declared-kinds.ts +40 -1
  629. package/src/workspace/delivery-metrics.schema.json +519 -0
  630. package/src/workspace/delivery-metrics.test.ts +236 -0
  631. package/src/workspace/delivery-metrics.ts +286 -0
  632. package/src/workspace/flags-read.ts +121 -0
  633. package/src/workspace/flags.test.ts +141 -0
  634. package/src/workspace/flags.ts +157 -0
  635. package/src/workspace/graph-cli.ts +1 -1
  636. package/src/workspace/identity-gate-rule.ts +91 -7
  637. package/src/workspace/identity.ts +14 -1
  638. package/src/workspace/intent-cli.ts +46 -5
  639. package/src/workspace/intent-trace.test.ts +173 -0
  640. package/src/workspace/intent-trace.ts +484 -0
  641. package/src/workspace/intent.schema.json +702 -6
  642. package/src/workspace/intent.ts +196 -26
  643. package/src/workspace/ls.schema.json +3 -1
  644. package/src/workspace/ls.ts +6 -0
  645. package/src/workspace/member-pipeline.test.ts +17 -0
  646. package/src/workspace/member-pipeline.ts +9 -0
  647. package/src/workspace/older-records.test.ts +412 -0
  648. package/src/workspace/overlap.test.ts +165 -0
  649. package/src/workspace/overlap.ts +340 -0
  650. package/src/workspace/promotion-order.ts +97 -0
  651. package/src/workspace/read-contract.e2e.test.ts +5 -1
  652. package/src/workspace/reason-codes.test.ts +7 -1
  653. package/src/workspace/reason-codes.ts +7 -1
  654. package/src/workspace/record-assets.test.ts +2 -1
  655. package/src/workspace/records-contract.e2e.test.ts +2 -1
  656. package/src/workspace/records.schema.json +21 -2
  657. package/src/workspace/records.ts +36 -2
  658. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  659. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  660. package/src/workspace/releases.schema.json +513 -0
  661. package/src/workspace/releases.test.ts +108 -0
  662. package/src/workspace/releases.ts +133 -0
  663. package/src/workspace/run-event.schema.json +295 -0
  664. package/src/workspace/status-contract.test.ts +81 -1
  665. package/src/workspace/status-gates.ts +64 -1
  666. package/src/workspace/status-mixed.test.ts +152 -0
  667. package/src/workspace/status-revoked.test.ts +153 -0
  668. package/src/workspace/status-stewards.ts +6 -0
  669. package/src/workspace/status.schema.json +268 -2
  670. package/src/workspace/status.ts +334 -4
  671. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  672. package/src/workspace/trust/op-run-statement.ts +148 -0
  673. package/src/workspace/trust/rotation.ts +61 -14
  674. package/src/workspace/trust/seal.ts +10 -0
  675. package/src/workspace/trust/ssh-commit.ts +10 -9
  676. package/src/workspace/wip.ts +11 -2
  677. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  678. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  679. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  680. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  681. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Feature flags as workspace records (#4024): the flag kind's records on
3
+ * read, the stale-flag check WSP118, the flags of a member by parameter, and
4
+ * `status --json`'s `featureFlags`, on the fixture in
5
+ * `__fixtures__/flag-workspace.ts`.
6
+ */
7
+
8
+ import { join } from "node:path";
9
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
10
+ import { cleanScratch, contract } from "./__fixtures__/contract-repo";
11
+ import { flagWorkspace, flagRecord } from "./__fixtures__/flag-workspace";
12
+ import { runDeclarationChecks } from "./checks";
13
+ import { memberFlagBindings, readWorkspaceFlags } from "./flags-read";
14
+ import { loadRecordKind } from "./records";
15
+ import { queryRecords } from "./records-cli";
16
+ import recordsSchema from "./records.schema.json";
17
+ import { workspaceStatus } from "./status";
18
+ import statusSchema from "./status.schema.json";
19
+
20
+ let root: string;
21
+ beforeAll(() => {
22
+ root = flagWorkspace();
23
+ });
24
+ afterAll(cleanScratch);
25
+
26
+ const KIND = "flags/flag.kind.mjs";
27
+
28
+ describe("the flag kind (#4024)", () => {
29
+ test("the canonical kind chant ships loads, with its flag block and schema", async () => {
30
+ const loaded = await loadRecordKind(join(import.meta.dirname, "reference-kinds", "flag", "flag.kind.mjs"));
31
+ expect(loaded.kind.name).toBe("flag");
32
+ expect(loaded.kind.flag).toEqual({ member: "member", param: "param", owner: "owner", values: "values", work: { field: "work", kind: "../work/work.kind.mjs" } });
33
+ expect(loaded.schema.$id).toBe("urn:intentius:chant:flag:1");
34
+ });
35
+
36
+ test("a kind with a flag block and no idField is refused", async () => {
37
+ const dir = flagWorkspace({
38
+ [KIND]: `export const recordKind = { name: "flag", location: { dir: ".", match: "^x$" }, format: "json", schema: { id: "urn:intentius:chant:flag:1", path: "flag.schema.json" }, idFrom: "sha256", flag: { member: "m", param: "p", owner: "o", values: "v", work: { field: "w", kind: "../work/work.kind.mjs" } } };\n`,
39
+ });
40
+ await expect(loadRecordKind(join(dir, KIND))).rejects.toThrow(/a flag kind names its id with idField/);
41
+ });
42
+
43
+ test("records gives each flag its work item, the decisions it implements, and whether it is stale", async () => {
44
+ const doc = await queryRecords({ kind: join(root, KIND), cwd: root, workGaps: false });
45
+ contract(recordsSchema).expectValid(doc);
46
+ if ("error" in doc || !("records" in doc)) throw new Error(JSON.stringify(doc));
47
+ const byId = new Map(doc.records.map((r) => [r.id, r]));
48
+ expect(byId.get("old-banner")).toMatchObject({
49
+ valid: true,
50
+ flag: { work: { id: "W-001", state: "done" }, implements: [{ id: "d-001", state: "confirmed", confirmed: true }], stale: true },
51
+ });
52
+ expect(byId.get("old-banner")!.warnings.map((w) => w.code)).toEqual(["flag-stale"]);
53
+ expect(byId.get("new-checkout")).toMatchObject({
54
+ valid: true,
55
+ flag: { work: { id: "W-002", state: "in-progress" }, implements: [{ id: "d-002", state: "decided", confirmed: false }], stale: false },
56
+ });
57
+ expect(byId.get("new-checkout")!.warnings).toEqual([]);
58
+ });
59
+
60
+ test("a flag that names no known work item is warned flag-work-unknown and is never stale", async () => {
61
+ const dir = flagWorkspace({ "flags/ghost.flag.md": flagRecord("ghost", { work: "W-404", member: "shop", param: "ghost", values: {} }) });
62
+ const doc = await queryRecords({ kind: join(dir, KIND), cwd: dir, workGaps: false });
63
+ if ("error" in doc || !("records" in doc)) throw new Error(JSON.stringify(doc));
64
+ const ghost = doc.records.find((r) => r.id === "ghost")!;
65
+ expect(ghost.flag).toEqual({ work: { id: "W-404", state: null }, implements: [], stale: false });
66
+ expect(ghost.warnings.map((w) => w.code)).toEqual(["flag-work-unknown"]);
67
+ });
68
+
69
+ test("a superseded decision is not confirmed, so its flag is not stale", async () => {
70
+ const dir = flagWorkspace({
71
+ "decisions/d-001-the-banner.md": `---\nid: "d-001"\nstate: "confirmed"\nsupersedes: []\n---\n`,
72
+ "decisions/d-003-a-new-banner.md": `---\nid: "d-003"\nstate: "confirmed"\nsupersedes: ["d-001"]\n---\n`,
73
+ });
74
+ const { flags } = await readWorkspaceFlags(dir);
75
+ expect(flags.find((f) => f.id === "old-banner")).toMatchObject({ implements: [{ id: "d-001", confirmed: false }], stale: false });
76
+ });
77
+ });
78
+
79
+ describe("WSP118, the stale-flag check (#4024)", () => {
80
+ test("reports the flag whose work item's decisions are all confirmed, on its record, as a warning", async () => {
81
+ const result = await runDeclarationChecks(root);
82
+ const found = result.diagnostics.filter((d) => d.ruleId === "WSP118");
83
+ expect(found).toHaveLength(1);
84
+ expect(found[0]).toMatchObject({ severity: "warning", code: "flag-stale", file: "flags/old-banner.flag.md" });
85
+ expect(found[0].message).toMatch(/^flag old-banner is due for removal: its work item W-001 implements d-001 \(confirmed\)/);
86
+ });
87
+
88
+ test("reports nothing once the stale flag is removed", async () => {
89
+ const dir = flagWorkspace({ "flags/old-banner.flag.md": null });
90
+ const result = await runDeclarationChecks(dir);
91
+ expect(result.diagnostics.filter((d) => d.ruleId === "WSP118")).toEqual([]);
92
+ });
93
+ });
94
+
95
+ describe("a member's flags by parameter", () => {
96
+ test("names the flag and its value in the environment, and none where the record gives none", async () => {
97
+ expect(await memberFlagBindings(root, join(root, "shop"), "prod")).toEqual({
98
+ member: "shop",
99
+ byParam: { newCheckout: { flag: "new-checkout", value: false } },
100
+ errors: [],
101
+ });
102
+ expect((await memberFlagBindings(root, join(root, "shop"), "staging"))!.byParam).toEqual({ newCheckout: { flag: "new-checkout", value: undefined } });
103
+ });
104
+
105
+ test("is null for a directory no member holds", async () => {
106
+ expect(await memberFlagBindings(root, join(root, "flags"), "prod")).toBeNull();
107
+ });
108
+
109
+ test("two flags for one parameter, and an invalid flag record naming the member, are errors", async () => {
110
+ const dir = flagWorkspace({
111
+ "flags/twin.flag.md": flagRecord("twin", { work: "W-002", member: "shop", param: "newCheckout", values: { prod: true } }),
112
+ "flags/broken.flag.md": `---\nschema: 1\nid: "broken"\nmember: "shop"\n---\n`,
113
+ });
114
+ const bindings = await memberFlagBindings(dir, join(dir, "shop"), "prod");
115
+ expect(bindings!.errors.join("\n")).toMatch(/flags new-checkout and twin both set build parameter "newCheckout" of member shop/);
116
+ expect(bindings!.errors.join("\n")).toMatch(/flag record flags\/broken\.flag\.md is not valid/);
117
+ });
118
+ });
119
+
120
+ describe("status --json lists featureFlags per member (#4024)", () => {
121
+ test("each flag with its values per environment, its work item and whether it is stale", async () => {
122
+ const doc = await workspaceStatus({ cwd: root, env: "prod", readLedger: async () => ({ records: [], malformed: 0 }), readPromotionOrder: async () => [] });
123
+ contract(statusSchema).expectValid(doc);
124
+ if ("error" in doc) throw new Error(JSON.stringify(doc));
125
+ const shop = doc.members.find((m) => m.name === "shop")!;
126
+ expect(shop.featureFlags).toEqual([
127
+ {
128
+ id: "new-checkout",
129
+ param: "newCheckout",
130
+ owner: "alice",
131
+ values: { dev: true, prod: false },
132
+ work: { id: "W-002", state: "in-progress" },
133
+ implements: [{ id: "d-002", state: "decided", confirmed: false }],
134
+ stale: false,
135
+ kind: "flags/flag.kind.mjs",
136
+ path: "flags/new-checkout.flag.md",
137
+ },
138
+ ]);
139
+ expect(doc.members.find((m) => m.name === "blog")!.featureFlags!.map((f) => [f.id, f.stale])).toEqual([["old-banner", true]]);
140
+ });
141
+ });
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Feature flags as workspace records (#4024, decided in #4016).
3
+ *
4
+ * A flag is a record of a kind with a `flag` block (`records.ts`). It names a
5
+ * member, one of that member's declared build-time parameters, an owner, the
6
+ * work item the flag hides and a value per environment. The member's build
7
+ * reads it as that parameter (`../flag-params.ts`), so turning a flag on in
8
+ * an environment is a commit that changes the record, and the release cut
9
+ * from that commit carries the new value as a `params` entry whose `source`
10
+ * is `flag:<id>` (ws-104).
11
+ *
12
+ * This module and `./flags-read.ts` do three things:
13
+ *
14
+ * - {@link applyFlags}: on a `records` read of a flag kind, gives each record
15
+ * its work item with that item's state, the decisions the item implements,
16
+ * and whether the flag is due for removal, with the warnings
17
+ * `flag-work-unknown` and `flag-stale`.
18
+ * - `readWorkspaceFlags` (`./flags-read.ts`): every flag of every flag kind
19
+ * the declaration names, for `workspace status` (`featureFlags`) and the
20
+ * build.
21
+ * - `memberFlagBindings` (`./flags-read.ts`): the flags of the member holding
22
+ * a directory, by parameter, for one environment.
23
+ *
24
+ * The readers live apart so that `records.ts`, which loads this module for a
25
+ * flag kind, never reaches `records-cli.ts` and what it imports.
26
+ *
27
+ * A flag is due for removal when its work item implements at least one
28
+ * decision and every decision it implements is confirmed: in one of its
29
+ * kind's closed states and superseded by nothing. For chant's decision kind
30
+ * that is `ratified` (a superseded decision has a superseder). The work is
31
+ * then settled, and the flag that hid it is dead weight.
32
+ */
33
+
34
+ import { dirname } from "node:path";
35
+ import type { ReasonCode } from "./reason-codes";
36
+ import { loadRecordKind, readRecords, RecordReadError, type LoadedRecordKind, type ReadRecordsOptions, type RecordEntry } from "./records";
37
+ import type { WorkLink } from "./work";
38
+
39
+ /** Why a flag record carries a warning. Closed, like the record warning codes. */
40
+ export const FLAG_WARNING_CODES = [
41
+ /** The flag names a work item no record of its kind's work kind has. */
42
+ "flag-work-unknown",
43
+ /** Every decision the flag's work item implements is confirmed, so the flag is due for removal. `check` reports it (WSP118). */
44
+ "flag-stale",
45
+ ] as const satisfies readonly ReasonCode[];
46
+ export type FlagWarningCode = (typeof FLAG_WARNING_CODES)[number];
47
+
48
+ /** A flag's value in one environment: what a build-time parameter can hold. */
49
+ export type FlagValue = string | number | boolean;
50
+
51
+ /** A decision a flag's work item implements, with whether it is confirmed. */
52
+ export interface FlagDecision extends WorkLink {
53
+ /** In one of its kind's closed states and superseded by nothing. */
54
+ confirmed: boolean;
55
+ }
56
+
57
+ /** What a read of a flag kind adds to each record (#4024). */
58
+ export interface FlagState {
59
+ /** The work item the flag hides, with its state, or null when the record names none. The state is null when no record has the id. */
60
+ work: WorkLink | null;
61
+ /** Each decision the work item implements, with its state. Empty when the item is unknown or implements none. */
62
+ implements: FlagDecision[];
63
+ /** True when the work item implements at least one decision and every one is confirmed: the flag is due for removal. */
64
+ stale: boolean;
65
+ }
66
+
67
+ /** A flag as `workspace status` and the build read it. */
68
+ export interface WorkspaceFlag {
69
+ /** The flag's name, its record's id. */
70
+ id: string;
71
+ /** The kind file from the workspace root. */
72
+ kind: string;
73
+ /** The record file from the workspace root, with / separators. */
74
+ path: string;
75
+ member: string;
76
+ param: string;
77
+ owner: string;
78
+ /** The value per environment, as the record writes it, in key order. */
79
+ values: Record<string, FlagValue>;
80
+ work: WorkLink | null;
81
+ implements: FlagDecision[];
82
+ stale: boolean;
83
+ }
84
+
85
+ /** A flag record that is not valid, so no build trusts it. */
86
+ export interface InvalidFlag {
87
+ kind: string;
88
+ path: string;
89
+ /** The member the record names, or null when it names none that can be read. */
90
+ member: string | null;
91
+ message: string;
92
+ }
93
+
94
+ /** Every flag a workspace's declared flag kinds hold. */
95
+ export interface WorkspaceFlags {
96
+ flags: WorkspaceFlag[];
97
+ invalid: InvalidFlag[];
98
+ }
99
+
100
+ const str = (v: unknown): string | null => (typeof v === "string" && v !== "" ? v : null);
101
+
102
+ /**
103
+ * Whether a decision is confirmed: in one of its kind's `closed` states and
104
+ * superseded by nothing. An unknown decision is not. The overlap report
105
+ * (#4025) shares it: a work item is in flight while any decision it
106
+ * implements is not confirmed.
107
+ */
108
+ export function decisionConfirmed(decision: { state: string | null; supersededBy: string | null } | undefined, closed: ReadonlySet<string>): boolean {
109
+ return decision !== undefined && decision.state !== null && closed.has(decision.state) && decision.supersededBy === null;
110
+ }
111
+
112
+ /**
113
+ * Give each record of a flag kind its {@link FlagState} and warnings, in
114
+ * place. Throws a `RecordReadError` when the work kind it names can't be read
115
+ * or is not a work kind.
116
+ */
117
+ export async function applyFlags(loaded: LoadedRecordKind, entries: RecordEntry[], options: ReadRecordsOptions): Promise<void> {
118
+ const flag = loaded.kind.flag!;
119
+ const workKind = await loadRecordKind(flag.work.kind, dirname(loaded.file));
120
+ if (!workKind.kind.work) {
121
+ throw new RecordReadError("kind-invalid", `flag kind ${loaded.kind.name} names ${flag.work.kind} as its work kind, and that kind has no work block`);
122
+ }
123
+ const decisionKind = await loadRecordKind(workKind.kind.work.decisions, dirname(workKind.file));
124
+ const read = await readRecords(workKind, { root: options.root, source: options.source });
125
+ const items = new Map<string, RecordEntry>();
126
+ for (const r of read.records) if (r.id !== null && !items.has(r.id)) items.set(r.id, r);
127
+ const decisions = new Map((read.decisions ?? []).map((d) => [d.id, d]));
128
+ const closed = new Set(decisionKind.kind.closedStates ?? []);
129
+
130
+ for (const e of entries) {
131
+ if (e.data === null) continue;
132
+ const named = str(e.data[flag.work.field]);
133
+ if (named === null) {
134
+ e.flag = { work: null, implements: [], stale: false };
135
+ continue;
136
+ }
137
+ const item = items.get(named);
138
+ if (!item) {
139
+ e.flag = { work: { id: named, state: null }, implements: [], stale: false };
140
+ e.warnings.push({ code: "flag-work-unknown", message: `hides the work item ${named}, which no record in ${flag.work.kind} has` });
141
+ continue;
142
+ }
143
+ const implementsList: FlagDecision[] = (item.implements ?? []).map((d) => {
144
+ const decision = decisions.get(d.id);
145
+ return { ...d, confirmed: decisionConfirmed(decision, closed) };
146
+ });
147
+ const stale = implementsList.length > 0 && implementsList.every((d) => d.confirmed);
148
+ e.flag = { work: { id: named, state: item.state }, implements: implementsList, stale };
149
+ if (stale) {
150
+ e.warnings.push({
151
+ code: "flag-stale",
152
+ message: `is due for removal: its work item ${named} implements ${implementsList.map((d) => `${d.id} (${d.state})`).join(", ")}, and every one is confirmed`,
153
+ });
154
+ }
155
+ }
156
+ }
157
+
@@ -60,7 +60,7 @@ export type GraphErrorCode = (typeof GRAPH_ERROR_CODES)[number];
60
60
  const LIVE_AT_REVISION = "--live reads the account as it stands now, and --at reads the source at a revision; chant workspace graph takes one of them";
61
61
 
62
62
  const USAGE =
63
- "chant workspace graph [dir] [--at <rev>] [--member <name>] [--kind <kind file>] [-o <file>] [--env <env>] [--live [--overlay] [--traffic <level>]] [--no-cache] [--dry-run] | chant workspace graph --composites [--at <rev>] [--member <name>] [-o <file>] | chant workspace graph --intent <path[:start-end]|path#symbol> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]";
63
+ "chant workspace graph [dir] [--at <rev>] [--member <name>] [--kind <kind file>] [-o <file>] [--env <env>] [--live [--overlay] [--traffic <level>]] [--no-cache] [--dry-run] | chant workspace graph --composites [--at <rev>] [--member <name>] [-o <file>] | chant workspace graph --intent <path[:start-end]|path#symbol|member/node|sha> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]";
64
64
 
65
65
  interface Head {
66
66
  $schema: string;
@@ -14,7 +14,7 @@
14
14
  * else: no zod, no ajv, no package loading, so it bundles small.
15
15
  */
16
16
 
17
- import { GATE_SEAL_NAMESPACE, gateSealPayload, namespaceAllowed, verifySshSignatureWith, type SealedGateApproval } from "./trust/gate-seal";
17
+ import { GATE_SEAL_NAMESPACE, gateSealPayload, namespaceAllowed, sshKeyFingerprint, sshSignatureKey, verifySshSignatureWith, type SealedGateApproval } from "./trust/gate-seal";
18
18
  import type { Signer } from "./trust/trust-files";
19
19
 
20
20
  /** The core classes, in the order a principal's roles are tried; `human` holds no role and is the rest. */
@@ -78,17 +78,101 @@ export function grantedClasses(classes: readonly ClassGrant[], roles: Readonly<R
78
78
  */
79
79
  export function admitApprover(rule: GateAdmissionRule, a: SealedGateApproval): string | null {
80
80
  const { gate, signers } = rule;
81
- const want = rule.class;
82
- if (want !== null && !rule.classes.some((c) => c.name === want)) {
83
- return `identity.gates.${gate} names the class ${want}, which ${rule.unknownClass ?? "no pinned package supplies"}, so no approval can show it is in that class (known classes: ${rule.classes.map((c) => c.name).join(", ")})`;
84
- }
81
+ const unknown = unknownClassRefusal(rule, "approval");
82
+ if (unknown !== null) return unknown;
85
83
  if (!signers.active) return `gate ${gate} needs a signed approval, and there is no signers file (${signers.signersPath}) at base to check one against`;
86
84
  const seal = sealRefusal(gate, signers, a);
87
85
  if (seal !== null) return seal;
86
+ return classRefusal(rule, a.resolvedBy);
87
+ }
88
+
89
+ /** Why no signature can show a principal is in the rule's class: the class is one the rule does not know. Null otherwise. */
90
+ function unknownClassRefusal(rule: GateAdmissionRule, what: string): string | null {
91
+ const want = rule.class;
92
+ if (want === null || rule.classes.some((c) => c.name === want)) return null;
93
+ return `identity.gates.${rule.gate} names the class ${want}, which ${rule.unknownClass ?? "no pinned package supplies"}, so no ${what} can show it is in that class (known classes: ${rule.classes.map((c) => c.name).join(", ")})`;
94
+ }
95
+
96
+ /** Why `principal` is not in the rule's class, or null when it is or the rule names none. */
97
+ function classRefusal(rule: GateAdmissionRule, principal: string): string | null {
98
+ const want = rule.class;
88
99
  if (want === null) return null;
89
- const granted = grantedClasses(rule.classes, rule.roles, a.resolvedBy);
100
+ const granted = grantedClasses(rule.classes, rule.roles, principal);
90
101
  const inClass = want === "human" ? granted.length === 0 : granted.includes(want);
91
- return inClass ? null : `${a.resolvedBy} is not in the class ${want} that gate ${gate} needs: no role grant at base puts them there`;
102
+ return inClass ? null : `${principal} is not in the class ${want} that gate ${rule.gate} needs: no role grant at base puts them there`;
103
+ }
104
+
105
+ // ── release cuts (#4031) ─────────────────────────────────────────────────
106
+
107
+ /** The gate whose `identity.gates` entry says who may cut a release (#4031). */
108
+ export const RELEASE_GATE = "release";
109
+
110
+ /** The ssh signature namespace of a release cut's signature (#4031), so no other signature can stand in for one. */
111
+ export const RELEASE_SEAL_NAMESPACE = "chant-release";
112
+
113
+ /** A cut line's fields its signature covers (#4031), and the signature. */
114
+ export interface SignedReleaseCut {
115
+ releaseDigest: string;
116
+ label: string | null;
117
+ actor: string;
118
+ timestamp: string;
119
+ /** The armored ssh signature, or null for an unsigned cut. */
120
+ signature: string | null;
121
+ }
122
+
123
+ /**
124
+ * The bytes a release cut's signature signs (#4031): the release digest,
125
+ * the label (empty for none), the actor who cut it and the cut's time,
126
+ * joined by LF with no final newline.
127
+ */
128
+ export function releaseSealPayload(cut: Omit<SignedReleaseCut, "signature">): Buffer {
129
+ return Buffer.from(`${cut.releaseDigest}\n${cut.label ?? ""}\n${cut.actor}\n${cut.timestamp}`, "utf-8");
130
+ }
131
+
132
+ /** Why the cut's signature does not verify for its actor against the signers at base, or null when it does. */
133
+ function releaseSignatureRefusal(signers: GateAdmissionRule["signers"], cut: SignedReleaseCut): string | null {
134
+ if (cut.signature === null) return `the cut by ${cut.actor} is not signed`;
135
+ if (!signers.active) return `there is no signers file (${signers.signersPath}) at base to check the cut's signature against`;
136
+ const where = `${signers.signersPath} at base`;
137
+ const listed = signers.signers.filter((s) => normalise(s.principal) === normalise(cut.actor));
138
+ if (listed.length === 0) return `${cut.actor} has no key in ${where}, so the cut's signature can't count`;
139
+ const keys = listed.filter((s) => namespaceAllowed(s.namespaces, RELEASE_SEAL_NAMESPACE)).map((s) => Buffer.from(s.key.split(" ")[1] ?? "", "base64"));
140
+ const r = verifySshSignatureWith(cut.signature, releaseSealPayload(cut), RELEASE_SEAL_NAMESPACE, keys);
141
+ return r.ok ? null : `the cut's signature does not verify for ${cut.actor} against ${where}: ${r.reason}`;
142
+ }
143
+
144
+ /**
145
+ * Why `cut` may not be made under the `release` gate's rule, or null when
146
+ * it may (#4031): its signature verifies for its actor against the signers
147
+ * at base and, when the gate names a class, the role grants at base put the
148
+ * actor in it. The same rule {@link admitApprover} applies to an approval,
149
+ * over the cut's own signed bytes.
150
+ */
151
+ export function admitReleaseCut(rule: GateAdmissionRule, cut: SignedReleaseCut): string | null {
152
+ const unknown = unknownClassRefusal(rule, "cut");
153
+ if (unknown !== null) return unknown;
154
+ const seal = releaseSignatureRefusal(rule.signers, cut);
155
+ if (seal !== null) return `${seal}; identity.gates.${rule.gate} needs a release cut signed with chant release cut --sign`;
156
+ return classRefusal(rule, cut.actor);
157
+ }
158
+
159
+ /** Whether a cut's signature verifies, as `release list` and `workspace status` report it (#4031). */
160
+ export interface ReleaseSignatureCheck {
161
+ /** `verified`: it verifies for the actor, who is in the release gate's class when it names one. `unsigned`: the cut has none. `unverified`: it does not, and `reason` says why. */
162
+ status: "verified" | "unsigned" | "unverified";
163
+ /** The fingerprint of the key in the signature, or null for an unsigned cut or one that can't be read. */
164
+ key: string | null;
165
+ reason: string;
166
+ }
167
+
168
+ /** Judge a stored cut's signature against `rule`, read at base. A rule with no class checks the signature alone. */
169
+ export function checkReleaseSignature(rule: GateAdmissionRule, cut: SignedReleaseCut): ReleaseSignatureCheck {
170
+ if (cut.signature === null) return { status: "unsigned", key: null, reason: "the cut carries no signature" };
171
+ const blob = sshSignatureKey(cut.signature);
172
+ const key = blob ? sshKeyFingerprint(blob) : null;
173
+ const why = unknownClassRefusal(rule, "cut") ?? releaseSignatureRefusal(rule.signers, cut) ?? classRefusal(rule, cut.actor);
174
+ if (why !== null) return { status: "unverified", key, reason: why };
175
+ return { status: "verified", key, reason: `signed by ${cut.actor}${rule.class !== null ? `, in the class ${rule.class}` : ""}` };
92
176
  }
93
177
 
94
178
  /** The seal step, worded as `checkGateSeal` (`./trust/seal.ts`) words it against an active signers file at base. */
@@ -26,7 +26,7 @@
26
26
  */
27
27
 
28
28
  import type { Declaration, SignedGate } from "./declaration";
29
- import { admitApprover } from "./identity-gate-rule";
29
+ import { admitApprover, RELEASE_GATE, type GateAdmissionRule } from "./identity-gate-rule";
30
30
  import { classesOf, coreClassRegistry, roleGrants, type ClassRegistry } from "./principal-classes";
31
31
  import type { ReasonCode } from "./reason-codes";
32
32
  import { normalisePrincipal } from "./records";
@@ -165,6 +165,19 @@ export function gateAdmissionFrom(source: GateRuleSource, gate: string): GateAdm
165
165
  return { requirement, refuses: (a) => admitApprover(rule, a) };
166
166
  }
167
167
 
168
+ /**
169
+ * The rule a release cut is judged by (#4031): the declaration's
170
+ * `identity.gates.release` entry with its class, read at base, or, when the
171
+ * declaration names no `release` gate, a rule with no class, which checks a
172
+ * cut's signature alone. `requirement` is null in that case, and a cut may
173
+ * then be made unsigned.
174
+ */
175
+ export function releaseRuleFrom(source: GateRuleSource): { requirement: SignedGate | null; rule: GateAdmissionRule } {
176
+ const requirement = source.declaration?.identity?.gates[RELEASE_GATE] ?? null;
177
+ const { policy, classes } = source;
178
+ return { requirement, rule: { gate: RELEASE_GATE, class: requirement?.class ?? null, signers: policy, roles: roleGrants(policy), classes: classes.all() } };
179
+ }
180
+
168
181
  /**
169
182
  * The rule for `gate` in the workspace holding `cwd`, read at base (the
170
183
  * target branch tip), or null when there is no declaration or it names none.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `chant workspace graph --intent <path[:start-end]|path#symbol> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]`
2
+ * `chant workspace graph --intent <path[:start-end]|path#symbol|member/node|sha> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]`
3
3
  * (#2651, #3034): the intent graph over one region (`intent.ts`), printed as JSON
4
4
  * with `--json` or as a walk, one line per node, in the order of #2650
5
5
  * section B: the region, its decisions, their artifacts, the commits, and the
@@ -7,16 +7,19 @@
7
7
  * the decisions governing it, most relevant first. Under each decision come the commits made inside its window, and
8
8
  * when any of them is not the decision's own work, the question #2650 B puts
9
9
  * to the person about it (#2656). Without `--kind`, the walk reads every
10
- * record kind the declaration names (#2680).
10
+ * record kind the declaration names (#2680). Last comes the why chain (#4023):
11
+ * each commit's work item, ci tag and release, and each release's gates and
12
+ * deploys.
11
13
  */
12
14
 
13
15
  import { resolve } from "node:path";
14
16
  import { formatError } from "../cli/format";
15
17
  import type { CommandContext } from "../cli/registry";
16
18
  import { intentRecord, type IntentRecordDocument } from "./intent-record";
19
+ import type { IntentTrace, TraceGate } from "./intent-trace";
17
20
  import { intentGraph, type ArtifactNode, type CommitNode, type DecisionNode, type IntentDocument, type IntentEdge, type IntentNode, type RunNode, type WhyAnswer, type WorkNode } from "./intent";
18
21
 
19
- const USAGE = "chant workspace graph --intent <path[:start-end]|path#symbol> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]";
22
+ const USAGE = "chant workspace graph --intent <path[:start-end]|path#symbol|member/node|sha> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]";
20
23
  const RECORD_USAGE = "chant workspace graph --intent --record <id> [--at <rev>] [--kind <kind file>...] [--follow-squash] [--json]";
21
24
 
22
25
  type Result = Exclude<IntentDocument, { error: unknown }>;
@@ -162,6 +165,42 @@ export function whyLines(doc: Result, why: WhyAnswer): string[] {
162
165
  return out;
163
166
  }
164
167
 
168
+ const gateLabel = (g: TraceGate) =>
169
+ `${g.op}/${g.gate}${g.environment ? ` ${g.environment}` : ""}: ${g.empty ? "passed over an empty change set" : `${g.resolvedBy}${g.relayedBy ? ` relayed by ${g.relayedBy}` : ""}`}${g.channel ? ` on ${g.channel}` : ""} at ${g.at}${g.planDigest ? `, plan ${g.planDigest.slice(0, 24)}${g.changeSet ? " (change set stored)" : ""}` : ""}${g.sealed ? ", sealed" : ""}`;
170
+
171
+ /**
172
+ * The why chain (#4023), in its order: for each commit that names a work item
173
+ * or was tagged, released or deployed, a line; then each release with its
174
+ * cuts, gates and deploys, and the deploys of no release.
175
+ */
176
+ export function traceLines(trace: IntentTrace): string[] {
177
+ const out: string[] = [];
178
+ const label = (digest: string) => trace.releases.find((r) => r.releaseDigest === digest)?.label ?? digest.slice(0, 24);
179
+ for (const c of trace.commits) {
180
+ const parts: string[] = [];
181
+ if (c.work && c.work.state !== "unlinked") parts.push(c.work.state === "linked" ? `work ${c.work.items.map((w) => `${w.id} (${w.source.field})`).join(", ") || "by a lease not checked"}` : `work ${c.work.state}`);
182
+ if (c.windowMatches.length > 0) parts.push(`in the window of ${c.windowMatches.map((w) => w.id).join(", ")}`);
183
+ if (c.tag.state !== "none") parts.push(c.tag.state);
184
+ for (const r of c.releases) parts.push(`${r.link === "cut-from" ? "released as" : "shipped in"} ${label(r.release)}`);
185
+ for (const d of c.deploys) parts.push(`${d.env} since ${trace.deploys.find((x) => x.id === d.deploy)?.timestamp ?? "?"}`);
186
+ if (parts.length > 0) out.push(`trace ${short(c.sha)} ${parts.join("; ")}`);
187
+ }
188
+ for (const r of trace.releases) {
189
+ const cut = r.cuts[0];
190
+ out.push(`release ${r.label ?? r.releaseDigest.slice(0, 24)} from ${short(r.commit)}, ${r.tag.state}${cut ? `; cut by ${cut.actor} at ${cut.timestamp}${cut.signed ? ", signed" : ""}` : ""}`);
191
+ for (const g of r.gates) out.push(` gate ${gateLabel(g)}`);
192
+ for (const id of r.deploys) {
193
+ const d = trace.deploys.find((x) => x.id === id);
194
+ if (d) out.push(` deploy ${d.env} ${d.component} at ${d.timestamp} by ${d.actor}${d.admittedBy ? `, admitted by ${d.admittedBy.op}/${d.admittedBy.gate} (${d.admittedBy.resolvedBy})` : ""}`);
195
+ }
196
+ }
197
+ for (const d of trace.deploys.filter((x) => x.release === null)) {
198
+ out.push(`deploy ${d.env} ${d.component} at ${d.timestamp} by ${d.actor} from ${short(d.gitSha)}, no release${d.admittedBy ? `; admitted by ${gateLabel(d.admittedBy)}` : ""}${d.plan ? `; plan ${d.plan.document.digest.slice(0, 24)}` : ""}`);
199
+ }
200
+ for (const x of trace.reasons) out.push(`reason ${x.code}: ${x.message}`);
201
+ return out;
202
+ }
203
+
165
204
  /** The walk as text, one line each, in the order of #2650 section B. */
166
205
  export function formatIntent(doc: Result): string {
167
206
  const out: string[] = [];
@@ -170,7 +209,8 @@ export function formatIntent(doc: Result): string {
170
209
  const lines = region.lines ? `:${region.lines.start}${region.lines.end === region.lines.start ? "" : `-${region.lines.end}`}` : "";
171
210
  const where = doc.at ? `at ${short(doc.at)}` : "in the working tree";
172
211
  const symbol = region.symbol ? `#${region.symbol.name} (${region.symbol.kind} ${region.symbol.qualified}, lines ${region.lines ? span(region.lines) : "none"})` : lines;
173
- out.push(`region ${region.path}${symbol} (${region.type}, member ${region.member ?? "none"}${region.generated ? ", generated" : ""}) ${where}${region.node ? `, from node ${region.node}` : ""}`);
212
+ if (region.commit) out.push(`region commit ${short(region.commit)} (member ${region.member ?? "none"}) ${doc.at ? `at ${short(doc.at)}` : "in the working tree"}`);
213
+ else out.push(`region ${region.path}${symbol} (${region.type}, member ${region.member ?? "none"}${region.generated ? ", generated" : ""}) ${where}${region.node ? `, from node ${region.node}` : ""}`);
174
214
  }
175
215
  const files = ofKind(doc, "file");
176
216
  if (files.length > 0) out.push(`files ${files.length} under the region, ${files.filter((f) => f.generated).length} generated`);
@@ -188,6 +228,7 @@ export function formatIntent(doc: Result): string {
188
228
  }
189
229
  for (const r of doc.reasons) out.push(`reason ${r.code}: ${r.message}`);
190
230
  if (doc.why) out.push(...whyLines(doc, doc.why));
231
+ if (doc.trace) out.push(...traceLines(doc.trace));
191
232
  const kinds = doc.kinds.length === 0 ? "; no --kind, so no decisions were read" : "";
192
233
  const work = ofKind(doc, "work").length;
193
234
  out.push(`${doc.summary.commits} commits, ${doc.summary.decisions} decisions, ${work > 0 ? `${work} work ${work === 1 ? "item" : "items"}, ` : ""}${doc.summary.artifacts} artifacts, ${doc.summary.findings} findings${kinds}`);
@@ -243,7 +284,7 @@ export async function runWorkspaceIntent(ctx: CommandContext, cwd: string): Prom
243
284
  return runRecord(ctx, cwd, args.kinds ?? (args.kind !== undefined ? [args.kind] : undefined));
244
285
  }
245
286
  if (!args.intent) {
246
- console.error(formatError({ message: "--intent needs a region: a path, path:line, path:start-end or path#symbol", hint: USAGE }));
287
+ console.error(formatError({ message: "--intent needs a region: a path, path:line, path:start-end, path#symbol, a graph node id or a commit sha", hint: USAGE }));
247
288
  return 1;
248
289
  }
249
290
  // No --kind: undefined, so the walk reads the kinds the declaration names (#2680).