@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,72 @@
1
+ /**
2
+ * Mapping a change set's entries onto graph node ids (#4022). The real-estate
3
+ * tests read the graph from `chant graph --format ir`: the five tofu roots in
4
+ * `lexicons/terraform/src/components/pr-loop.tofu.test.ts` and the aws
5
+ * workspace member in `lifecycle/gate-change-set.e2e.test.ts`. This holds the
6
+ * address rules and the digest.
7
+ */
8
+
9
+ import { readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { describe, expect, test } from "vitest";
12
+ import { composeChangeSet, lifecyclePlanPart, verifyChangeSetDigest, type ChangeSetEntry } from "./change-set";
13
+ import { changeSetNodeCandidates, mapChangeSetToGraph, terraformBlockKey } from "./change-set-graph";
14
+ import type { ChangeSet as LifecycleChangeSet } from "./lifecycle/change-set";
15
+
16
+ const plan = JSON.parse(readFileSync(join(import.meta.dirname, "__fixtures__", "change-set", "lifecycle-plan.floci.json"), "utf8")) as LifecycleChangeSet;
17
+
18
+ describe("terraformBlockKey", () => {
19
+ test("names the block the terraform lexicon keys, without the instance index", () => {
20
+ expect(terraformBlockKey("terraform_data.keep")).toBe("terraform_data.keep");
21
+ expect(terraformBlockKey("module.app.terraform_data.instance[0]")).toBe("module.app/terraform_data.instance");
22
+ expect(terraformBlockKey('module.cdn["eu"].module.bucket[1].aws_s3_bucket.this["a.b]"]')).toBe("module.cdn/module.bucket/aws_s3_bucket.this");
23
+ expect(terraformBlockKey("data.aws_iam_policy.boundary")).toBe("data.aws_iam_policy.boundary");
24
+ expect(terraformBlockKey("module.app.data.aws_ami.ubuntu")).toBe("module.app/data.aws_ami.ubuntu");
25
+ });
26
+
27
+ test("a terraform entry tries <root>/<block key>, then its address", () => {
28
+ expect(changeSetNodeCandidates({ member: "estate", planner: "tofu", address: "module.app.terraform_data.instance[1]" })).toEqual([
29
+ "estate/module.app/terraform_data.instance",
30
+ "module.app.terraform_data.instance[1]",
31
+ ]);
32
+ expect(changeSetNodeCandidates({ member: "delivery", planner: "chant", address: "jobs" })).toEqual(["jobs"]);
33
+ expect(changeSetNodeCandidates({ member: "org", planner: "warden", address: "team.platform" })).toEqual(["team.platform"]);
34
+ });
35
+ });
36
+
37
+ describe("mapChangeSetToGraph", () => {
38
+ const doc = composeChangeSet([lifecyclePlanPart({ member: "delivery", plan })]);
39
+
40
+ test("keeps the ids the graph holds, puts the member in front, and leaves the digest alone", () => {
41
+ const mapped = mapChangeSetToGraph(doc, { member: "delivery", at: "a".repeat(40), ids: ["jobs", "events", "dlq"] });
42
+ expect(mapped.entries.map((e) => [e.address, e.node])).toEqual([
43
+ ["alerts", null],
44
+ ["dlq", "delivery/dlq"],
45
+ ["events", "delivery/events"],
46
+ ["jobs", "delivery/jobs"],
47
+ ]);
48
+ expect(mapped.graph).toEqual({ member: "delivery", at: "a".repeat(40) });
49
+ expect(mapped.digest).toBe(doc.digest);
50
+ expect(verifyChangeSetDigest(mapped)).toBe(true);
51
+ });
52
+
53
+ test("outside a workspace the id is the member's own", () => {
54
+ const mapped = mapChangeSetToGraph(doc, { member: null, at: null, ids: ["jobs"] });
55
+ expect(mapped.entries.find((e) => e.address === "jobs")!.node).toBe("jobs");
56
+ });
57
+
58
+ test("no graph gives every entry a null node", () => {
59
+ const mapped = mapChangeSetToGraph(doc, null);
60
+ expect(mapped.graph).toBeNull();
61
+ expect(mapped.entries.every((e: ChangeSetEntry) => e.node === null)).toBe(true);
62
+ expect(verifyChangeSetDigest(mapped)).toBe(true);
63
+ });
64
+
65
+ test("a node is not hashed, and every other part of an entry still is", () => {
66
+ const mapped = mapChangeSetToGraph(doc, { member: null, at: null, ids: ["jobs"] });
67
+ const moved = { ...mapped, entries: mapped.entries.map((e) => ({ ...e, node: "elsewhere" })) };
68
+ expect(verifyChangeSetDigest(moved)).toBe(true);
69
+ const changed = { ...mapped, entries: mapped.entries.map((e, i) => (i === 0 ? { ...e, address: "other" } : e)) };
70
+ expect(verifyChangeSetDigest(changed)).toBe(false);
71
+ });
72
+ });
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Place a change set's entries on the graph (#4022).
3
+ *
4
+ * A reader such as behold draws the graph `chant graph --format ir` and
5
+ * `chant workspace graph` print, and wants each change of a change set on the
6
+ * node it changes. The planner's address is not that node's id: a terraform
7
+ * plan names an instance, `module.app.terraform_data.instance[0]`, where the
8
+ * graph has one node per block, `<root>/module.app/terraform_data.instance`
9
+ * (the terraform lexicon's entity key, `hcl/parse.ts`). So chant maps the
10
+ * address itself, against the node ids of the graph it read, and a reader
11
+ * never derives an id.
12
+ *
13
+ * {@link changeSetNodeCandidates} lists the ids an entry's address can have,
14
+ * by planner. {@link mapChangeSetToGraph} keeps the first that the graph
15
+ * holds and puts the member's name in front, as `chant workspace graph`
16
+ * does, with the node's source file as `source` when the graph gives one
17
+ * (#4025). An entry whose address the graph does not hold gets `node: null`:
18
+ * a delete of a block the source no longer declares, or a warden or sql
19
+ * change outside the graph.
20
+ *
21
+ * Like `./change-set.ts`, this imports nothing at run time, so it bundles
22
+ * into one file with the change-set code.
23
+ */
24
+
25
+ import type { ChangeSetDocument, ChangeSetEntry } from "./change-set";
26
+
27
+ /** The graph a document is mapped onto: the node ids of one member's own IR. */
28
+ export interface ChangeSetGraphNodes {
29
+ /** The workspace member whose graph it is, its path of names joined with `/` when nested, or null outside a workspace. */
30
+ member: string | null;
31
+ /** The commit the graph was read at, or null when unknown. */
32
+ at: string | null;
33
+ /** Every node id of the member's `chant graph --format ir`, without the member in front. */
34
+ ids: Iterable<string>;
35
+ /**
36
+ * The source file of each node that has one (#4025), by the same ids, as a
37
+ * path from the repository root. An entry mapped onto such a node gets it
38
+ * as `source`.
39
+ */
40
+ sources?: ReadonlyMap<string, string>;
41
+ }
42
+
43
+ /**
44
+ * Split a terraform address into its dot-separated parts, dropping every
45
+ * `[...]` index. A quoted key may hold `.` or `]`, so quotes are honoured.
46
+ */
47
+ function terraformParts(address: string): string[] {
48
+ const parts: string[] = [];
49
+ let current = "";
50
+ let depth = 0;
51
+ let quoted = false;
52
+ for (let i = 0; i < address.length; i++) {
53
+ const c = address[i];
54
+ if (depth > 0) {
55
+ if (quoted) {
56
+ if (c === "\\") i++;
57
+ else if (c === '"') quoted = false;
58
+ } else if (c === '"') quoted = true;
59
+ else if (c === "]") depth--;
60
+ continue;
61
+ }
62
+ if (c === "[") depth++;
63
+ else if (c === ".") {
64
+ parts.push(current);
65
+ current = "";
66
+ } else current += c;
67
+ }
68
+ parts.push(current);
69
+ return parts.filter((p) => p.length > 0);
70
+ }
71
+
72
+ /**
73
+ * The block key the terraform lexicon gives an address in its root:
74
+ * `module.<call>/` for each module call, then the block's own address with
75
+ * no index. `module.app.terraform_data.instance[0]` is
76
+ * `module.app/terraform_data.instance`.
77
+ */
78
+ export function terraformBlockKey(address: string): string | undefined {
79
+ const parts = terraformParts(address);
80
+ const scope: string[] = [];
81
+ let i = 0;
82
+ while (parts[i] === "module" && i + 1 < parts.length) {
83
+ scope.push(`module.${parts[i + 1]}`);
84
+ i += 2;
85
+ }
86
+ const rest = parts.slice(i);
87
+ let block: string | undefined;
88
+ if (rest[0] === "data" && rest.length >= 3) block = `data.${rest[1]}.${rest[2]}`;
89
+ else if (rest.length >= 2) block = `${rest[0]}.${rest[1]}`;
90
+ else if (rest.length === 0 && scope.length > 0) {
91
+ // The module call itself is a block of its caller.
92
+ const call = scope.pop()!;
93
+ block = call;
94
+ }
95
+ if (block === undefined) return undefined;
96
+ return [...scope, block].join("/");
97
+ }
98
+
99
+ /**
100
+ * The ids, in the order tried, that an entry's address can have in its
101
+ * member's graph IR:
102
+ *
103
+ * - `chant`: the address, which is the entity name the IR uses as its id.
104
+ * - `terraform`, `tofu`, `choudoufu`: `<root>/<block key>`, the root being
105
+ * the change-set member (`terraformBlockKey`), then the address.
106
+ * - `warden`, `sql`: the address.
107
+ */
108
+ export function changeSetNodeCandidates(entry: Pick<ChangeSetEntry, "member" | "planner" | "address">): string[] {
109
+ switch (entry.planner) {
110
+ case "terraform":
111
+ case "tofu":
112
+ case "choudoufu": {
113
+ const key = terraformBlockKey(entry.address);
114
+ return key === undefined ? [entry.address] : [`${entry.member}/${key}`, entry.address];
115
+ }
116
+ default:
117
+ return [entry.address];
118
+ }
119
+ }
120
+
121
+ /**
122
+ * The document with each entry's `node` set from `graph`, and `graph`
123
+ * recording which graph it was. A null `graph` (none could be read) gives
124
+ * every entry `node: null`. The digest is unchanged: `node` is not hashed.
125
+ */
126
+ export function mapChangeSetToGraph(doc: ChangeSetDocument, graph: ChangeSetGraphNodes | null): ChangeSetDocument {
127
+ if (graph === null) {
128
+ return { ...doc, entries: doc.entries.map((e) => ({ ...e, node: null })), graph: null };
129
+ }
130
+ const ids = new Set(graph.ids);
131
+ const prefix = graph.member ? `${graph.member}/` : "";
132
+ return {
133
+ ...doc,
134
+ entries: doc.entries.map((e) => {
135
+ const id = changeSetNodeCandidates(e).find((c) => ids.has(c));
136
+ const source = id === undefined ? undefined : graph.sources?.get(id);
137
+ return { ...e, node: id === undefined ? null : `${prefix}${id}`, ...(source !== undefined ? { source } : {}) };
138
+ }),
139
+ graph: { member: graph.member, at: graph.at },
140
+ };
141
+ }
package/src/change-set.ts CHANGED
@@ -113,6 +113,25 @@ export interface ChangeSetEntry {
113
113
  /** The estate, environment or org the change lands in. */
114
114
  scope?: string;
115
115
  attributes: ChangeSetAttribute[];
116
+ /**
117
+ * The graph node the entry changes, as `chant graph --format ir` and
118
+ * `chant workspace graph` name it (#4022): `<member>/<id>` inside a
119
+ * workspace. Null when the graph holds no node for the address, such as
120
+ * a delete of a block the source no longer declares. Absent until the
121
+ * document is mapped onto a graph (`./change-set-graph.ts`), and never
122
+ * part of the digest.
123
+ */
124
+ node?: string | null;
125
+ /**
126
+ * The source file that declares the entry's node, from the repository
127
+ * root (#4025): the node's `sourceLoc.file` in the member's graph IR, with
128
+ * the member's directory in front. Set beside `node` when a stored change
129
+ * set is mapped onto a graph whose node has a source location, absent
130
+ * otherwise. Like `node`, never part of the digest. The overlap report
131
+ * attributes the entry to a work item through the commits that changed
132
+ * this file.
133
+ */
134
+ source?: string;
116
135
  }
117
136
 
118
137
  /** Something a planner could not read, so the plan says nothing about it. */
@@ -201,6 +220,30 @@ export interface ChangeSetDocument {
201
220
  /** Triggered actions, sorted by member and address. Absent when no member runs one. */
202
221
  sideEffects?: ChangeSetSideEffect[];
203
222
  summary: ChangeSetSummary;
223
+ /**
224
+ * The graph the entries' `node` ids come from (#4022): the workspace member
225
+ * whose graph it is, or null outside a workspace, and the commit it was
226
+ * read at, or null when that is unknown. Null when no graph could be read,
227
+ * so every `node` is null. Absent on a document never mapped. Not hashed.
228
+ */
229
+ graph?: ChangeSetGraph | null;
230
+ /**
231
+ * The release this change set was planned for (#4045): the `releaseDigest`
232
+ * of the release a `components promote --release` deploys. Set with
233
+ * `environment` on a release gate's document, absent on every other. Part
234
+ * of the digest when present, so a stored document names the one release
235
+ * and environment its gate bound, and two releases whose plans read the
236
+ * same never share a document or an approval.
237
+ */
238
+ releaseDigest?: string;
239
+ /** The environment this change set was planned for (#4045), set with `releaseDigest`. Part of the digest when present. */
240
+ environment?: string;
241
+ }
242
+
243
+ /** Where a document's node ids come from. */
244
+ export interface ChangeSetGraph {
245
+ member: string | null;
246
+ at: string | null;
204
247
  }
205
248
 
206
249
  const byString = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);
@@ -235,22 +278,40 @@ export function changeSetDigest(members: ReadonlyArray<Pick<ChangeSetMember, "me
235
278
  /**
236
279
  * The document's digest: `computePlanDigest("change-set-document", …)` over
237
280
  * the set digest ({@link changeSetDigest}), each member's name, status and
238
- * holes, the entries and the side effects. Members are sorted by name and
281
+ * holes, the entries (without their graph `node`) and the side effects.
282
+ * Members are sorted by name and
239
283
  * entries and side effects by the keys {@link composeChangeSet} sorts them by,
240
284
  * so order makes no difference.
241
285
  *
242
286
  * A provisional member is left out, with its entries and side effects, as
243
287
  * the set digest leaves it out: no approval of the document covers it.
288
+ *
289
+ * A release gate's document (#4045) also hashes the `releaseDigest` and
290
+ * `environment` it was planned for. A document without them hashes as it
291
+ * always has, so no earlier digest moves.
244
292
  */
245
- export function changeSetDocumentDigest(doc: Pick<ChangeSetDocument, "members" | "entries" | "sideEffects">): string {
293
+ export function changeSetDocumentDigest(
294
+ doc: Pick<ChangeSetDocument, "members" | "entries" | "sideEffects" | "releaseDigest" | "environment">,
295
+ ): string {
246
296
  const provisional = new Set(doc.members.filter((m) => m.provisional === true).map((m) => m.member));
247
297
  const members = doc.members
248
298
  .filter((m) => !provisional.has(m.member))
249
299
  .map((m) => ({ member: m.member, status: m.status, holes: m.holes }))
250
300
  .sort((a, b) => byString(a.member, b.member));
251
- const entries = doc.entries.filter((e) => !provisional.has(e.member)).sort((a, b) => byString(entryKey(a), entryKey(b)));
301
+ // An entry's graph node (#4022) and its source file (#4025) say where on
302
+ // the graph the change is, not what the plan does, so they stay outside
303
+ // the digest: mapping a stored
304
+ // document onto a graph never moves the value its gate bound.
305
+ const entries = doc.entries
306
+ .filter((e) => !provisional.has(e.member))
307
+ .map(({ node: _node, source: _source, ...e }) => e)
308
+ .sort((a, b) => byString(entryKey(a), entryKey(b)));
252
309
  const sideEffects = (doc.sideEffects ?? []).filter((s) => !provisional.has(s.member)).sort(bySideEffect);
253
- return computePlanDigest("change-set-document", { set: changeSetDigest(doc.members), members, entries, sideEffects });
310
+ const planned =
311
+ doc.releaseDigest !== undefined || doc.environment !== undefined
312
+ ? { planned: { releaseDigest: doc.releaseDigest ?? null, environment: doc.environment ?? null } }
313
+ : {};
314
+ return computePlanDigest("change-set-document", { set: changeSetDigest(doc.members), members, entries, sideEffects, ...planned });
254
315
  }
255
316
 
256
317
  /** The summary of a set of members and entries. */
@@ -296,6 +357,8 @@ function sortKeys<T>(o: Record<string, T>): Record<string, T> {
296
357
  export interface ComposeChangeSetOptions {
297
358
  /** The chant version to record in `chant`. */
298
359
  chant?: string;
360
+ /** The release and environment a release gate's change set was planned for (#4045), recorded and hashed. */
361
+ planned?: { releaseDigest: string; environment: string };
299
362
  }
300
363
 
301
364
  /**
@@ -317,12 +380,14 @@ export function composeChangeSet(parts: ReadonlyArray<ChangeSetPart>, options: C
317
380
  const sideEffects: ChangeSetSideEffect[] = parts
318
381
  .flatMap((p) => (p.sideEffects ?? []).map((s) => ({ member: p.member.member, ...s })))
319
382
  .sort(bySideEffect);
320
- const digest = changeSetDocumentDigest({ members, entries, sideEffects });
383
+ const planned = options.planned ? { releaseDigest: options.planned.releaseDigest, environment: options.planned.environment } : {};
384
+ const digest = changeSetDocumentDigest({ members, entries, sideEffects, ...planned });
321
385
  return {
322
386
  $schema: CHANGE_SET_SCHEMA_ID,
323
387
  contract: CHANGE_SET_CONTRACT,
324
388
  ...(options.chant !== undefined ? { chant: options.chant } : {}),
325
389
  digest,
390
+ ...planned,
326
391
  members,
327
392
  entries,
328
393
  ...(sideEffects.length > 0 ? { sideEffects } : {}),
@@ -335,7 +400,9 @@ export function composeChangeSet(parts: ReadonlyArray<ChangeSetPart>, options: C
335
400
  * effects give ({@link changeSetDocumentDigest}). A document that names a
336
401
  * member twice has no digest and is not verified.
337
402
  */
338
- export function verifyChangeSetDigest(doc: Pick<ChangeSetDocument, "digest" | "members" | "entries" | "sideEffects">): boolean {
403
+ export function verifyChangeSetDigest(
404
+ doc: Pick<ChangeSetDocument, "digest" | "members" | "entries" | "sideEffects" | "releaseDigest" | "environment">,
405
+ ): boolean {
339
406
  try {
340
407
  return changeSetDocumentDigest(doc) === doc.digest;
341
408
  } catch {
@@ -343,6 +410,20 @@ export function verifyChangeSetDigest(doc: Pick<ChangeSetDocument, "digest" | "m
343
410
  }
344
411
  }
345
412
 
413
+ /**
414
+ * Whether a change set changes nothing (ws-104 question 6, #4045): it has at
415
+ * least one member, every member planned, none failed or provisional, no
416
+ * member has a hole, and every entry is a `no-op` or a `read`. A release
417
+ * gate over such a document passes without an approval.
418
+ */
419
+ export function isEmptyChangeSet(doc: Pick<ChangeSetDocument, "members" | "entries">): boolean {
420
+ if (doc.members.length === 0) return false;
421
+ for (const m of doc.members) {
422
+ if (m.status !== "planned" || m.provisional === true || m.holes.length > 0) return false;
423
+ }
424
+ return doc.entries.every((e) => e.action === "no-op" || e.action === "read");
425
+ }
426
+
346
427
  // ── chant lifecycle plan ───────────────────────────────────────────────────────
347
428
 
348
429
  export interface LifecyclePlanPartInput {
@@ -0,0 +1,16 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ /**
4
+ * The installed chant's version, read from `@intentius/chant`'s own
5
+ * package.json, or "0.0.0" when it can't be read. The path is the same from
6
+ * `src/` and `dist/`, so it holds whether chant runs from source or from its
7
+ * build. Kept outside `cli/` so that a module the Op entry loads, such as the
8
+ * run-event writer (#4041), can name the version without loading the CLI.
9
+ */
10
+ export const CHANT_VERSION: string = (() => {
11
+ try {
12
+ return (createRequire(import.meta.url)("../package.json") as { version?: string }).version ?? "0.0.0";
13
+ } catch {
14
+ return "0.0.0";
15
+ }
16
+ })();
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from "fs";
2
2
  import { resolve } from "path";
3
- import { resolveBuildParams, type BuildParamsConfig } from "../build-params";
3
+ import { resolveBuildParams, type BuildParamFlags, type BuildParamsConfig } from "../build-params";
4
4
  import type { BuildParamProvenance } from "../provenance";
5
5
  import { formatError, formatInfo } from "./format";
6
6
 
@@ -40,6 +40,10 @@ export interface CliBuildParamsArgs {
40
40
  paramsFile?: string;
41
41
  /** `--verbose` — list every resolved parameter one per line instead of the one-line count (#1424). */
42
42
  verbose?: boolean;
43
+ /** The workspace flags backing parameters in this build's environment (#4024), from `../flag-params.ts`. */
44
+ flags?: BuildParamFlags;
45
+ /** `false` to resolve without printing the summary line, for a caller that checks more before it prints ({@link echoBuildParams}). */
46
+ echo?: boolean;
43
47
  }
44
48
 
45
49
  export interface CliBuildParamsResolution {
@@ -94,6 +98,7 @@ export function resolveCliBuildParams(
94
98
  cli: args.cli,
95
99
  fromFile,
96
100
  env: process.env,
101
+ ...(args.flags ? { flags: args.flags } : {}),
97
102
  });
98
103
  for (const message of resolution.errors) {
99
104
  errors.push(formatError({ message }));
@@ -103,21 +108,25 @@ export function resolveCliBuildParams(
103
108
  return { success: false, provenance: [], errors };
104
109
  }
105
110
 
111
+ if (args.echo !== false) echoBuildParams(resolution.provenance, args.verbose);
112
+ return { success: true, provenance: resolution.provenance, errors: [] };
113
+ }
114
+
115
+ /** Print a successful resolution: the one-line summary, or every value under `--verbose` or `CHANT_PARAM_ECHO=all` (#1424). */
116
+ export function echoBuildParams(provenance: readonly BuildParamProvenance[], verboseFlag?: boolean): void {
106
117
  // #1424 — one line by default. A project with two dozen declared
107
118
  // parameters printed two dozen grey lines on every build, ahead of the
108
119
  // warnings the reader actually needed. The count by source still answers
109
120
  // "did my override reach the build" (the failure mode
110
121
  // warnIfParamsCannotReachProject exists for); --verbose names every value.
111
- const verbose = args.verbose === true || process.env.CHANT_PARAM_ECHO === "all";
122
+ const verbose = verboseFlag === true || process.env.CHANT_PARAM_ECHO === "all";
112
123
  if (verbose) {
113
- for (const p of resolution.provenance) {
124
+ for (const p of provenance) {
114
125
  console.error(formatInfo(`[param] ${p.name} = ${JSON.stringify(p.value)} (${p.source})`));
115
126
  }
116
- } else if (resolution.provenance.length > 0) {
117
- console.error(formatInfo(summarizeBuildParams(resolution.provenance)));
127
+ } else if (provenance.length > 0) {
128
+ console.error(formatInfo(summarizeBuildParams(provenance)));
118
129
  }
119
-
120
- return { success: true, provenance: resolution.provenance, errors: [] };
121
130
  }
122
131
 
123
132
  /**
@@ -127,8 +136,12 @@ export function resolveCliBuildParams(
127
136
  */
128
137
  export function summarizeBuildParams(provenance: readonly { source: string }[]): string {
129
138
  const counts = new Map<string, number>();
130
- for (const p of provenance) counts.set(p.source, (counts.get(p.source) ?? 0) + 1);
131
- const order = ["cli", "params-file", "env", "default"];
139
+ // Every flag source (`flag:<name>`, #4024) counts as one, "from flags".
140
+ for (const p of provenance) {
141
+ const source = p.source.startsWith("flag:") ? "flags" : p.source;
142
+ counts.set(source, (counts.get(source) ?? 0) + 1);
143
+ }
144
+ const order = ["cli", "params-file", "env", "flags", "default"];
132
145
  const rank = (s: string) => (order.includes(s) ? order.indexOf(s) : order.length);
133
146
  const parts = [...counts.keys()]
134
147
  .sort((a, b) => rank(a) - rank(b))
@@ -12,7 +12,7 @@ import { resolveTelemetryAttribution } from "../../telemetry-attribution";
12
12
  import { loadOkfBundle } from "../../okf-read";
13
13
  import { unknownEnvError } from "../../env";
14
14
  import type { OwnershipMarker } from "../../ownership";
15
- import { resolveCliBuildParams } from "../build-params-cli";
15
+ import { resolveFlaggedBuildParams } from "../../flag-params";
16
16
  import type { Serializer, SerializerResult } from "../../serializer";
17
17
  import type { LexiconPlugin } from "../../lexicon";
18
18
  import { runPostSynthChecks, type PostSynthDiagnostic } from "../../lint/post-synth";
@@ -118,6 +118,20 @@ export interface BuildOptions {
118
118
  * outputs of its own `serializers`, so the sources are evaluated once.
119
119
  */
120
120
  sharedBuild?: { serializers: Serializer[]; result?: ReturnType<typeof build> };
121
+
122
+ /**
123
+ * Where each output file goes: called with its absolute path and its
124
+ * content in place of creating the directory and writing the file. A
125
+ * release cut (#4046) passes one that keeps the files in memory, so it
126
+ * records exactly what this build writes and nothing left on disk by an
127
+ * earlier one.
128
+ */
129
+ writeFile?: (path: string, content: string) => void;
130
+ }
131
+
132
+ function writeFileCreatingDir(path: string, content: string): void {
133
+ mkdirSync(dirname(path), { recursive: true });
134
+ writeFileSync(path, content);
121
135
  }
122
136
 
123
137
  /**
@@ -283,11 +297,14 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
283
297
  // project's own `params.ts` does before migrating to this mechanism). Also
284
298
  // logs a one-line count on success, or every resolved value (`[param] name =
285
299
  // value (source)`) under --verbose (#1424).
286
- const paramsResolution = resolveCliBuildParams(config.buildParams, {
300
+ // #4024: in a workspace member, a build for an environment also reads the
301
+ // flag records that back its parameters, and refuses an override of one
302
+ // where the environment has a gate (../../flag-params.ts).
303
+ const paramsResolution = await resolveFlaggedBuildParams(configDir, config.buildParams, {
287
304
  cli: options.params,
288
305
  paramsFile: options.paramsFile,
289
306
  verbose: options.verbose,
290
- });
307
+ }, options.env);
291
308
  if (!paramsResolution.success) {
292
309
  errors.push(...paramsResolution.errors);
293
310
  return { success: false, resourceCount: 0, fileCount: 0, errors, warnings };
@@ -735,6 +752,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
735
752
  }
736
753
 
737
754
  // Handle output
755
+ const writeOutput = options.writeFile ?? writeFileCreatingDir;
738
756
  if (result.errors.length === 0 && errors.length === 0) {
739
757
  // Extract primary content and collect additional files from SerializerResult.
740
758
  // Each entry remembers which lexicon produced it, so a same-basename
@@ -866,8 +884,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
866
884
  additionalFiles.delete(filename);
867
885
  try {
868
886
  const targetPath = join(projectDist, filename);
869
- mkdirSync(dirname(targetPath), { recursive: true });
870
- writeFileSync(targetPath, entry.content);
887
+ writeOutput(targetPath, entry.content);
871
888
  opsWritten += 1;
872
889
  } catch (err) {
873
890
  errors.push(
@@ -893,8 +910,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
893
910
  if (!config?.name) continue;
894
911
  try {
895
912
  const targetPath = join(projectDist, "ops", config.name, "op.json");
896
- mkdirSync(dirname(targetPath), { recursive: true });
897
- writeFileSync(targetPath, serializeOpIR(config));
913
+ writeOutput(targetPath, serializeOpIR(config));
898
914
  irWritten += 1;
899
915
  } catch (err) {
900
916
  errors.push(
@@ -913,8 +929,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
913
929
  // output path and any nested additional-file paths (e.g. ops/<name>/...).
914
930
  try {
915
931
  const outputPath = resolve(options.output);
916
- mkdirSync(dirname(outputPath), { recursive: true });
917
- writeFileSync(outputPath, output);
932
+ writeOutput(outputPath, output);
918
933
 
919
934
  // Write additional files (e.g. nested stack templates) alongside the primary output
920
935
  if (additionalFiles.size > 0) {
@@ -935,8 +950,7 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
935
950
  }
936
951
  }
937
952
  const targetPath = join(outputDir, filename);
938
- mkdirSync(dirname(targetPath), { recursive: true });
939
- writeFileSync(targetPath, fileContent);
953
+ writeOutput(targetPath, fileContent);
940
954
  }
941
955
  }
942
956
  } catch (err) {
@@ -88,3 +88,54 @@ describe("auditDocsReachability", () => {
88
88
  expect(auditDocsReachability(d).unreachable).toEqual(["a", "c"]);
89
89
  });
90
90
  });
91
+
92
+ /**
93
+ * chant #3993: inside the chant repository a lexicon's pages are generated
94
+ * into the main docs site with a _sidebar.json beside them, and the audit
95
+ * reads those instead of a docs app the lexicon no longer has.
96
+ */
97
+ describe("auditDocsReachability for a section of the main site", () => {
98
+ function mainSite(): void {
99
+ mkdirSync(join(dir, "docs"), { recursive: true });
100
+ writeFileSync(join(dir, "docs", "package.json"), JSON.stringify({ name: "@intentius/chant-docs" }));
101
+ writeFileSync(join(dir, "docs", "astro.config.mjs"), "export default {};\n");
102
+ }
103
+
104
+ function repoWithSection(pages: string[], group: unknown, hidden: string[] = []): string {
105
+ mainSite();
106
+ const section = join(dir, "docs", "src", "content", "docs", "lexicons", "fixture");
107
+ mkdirSync(section, { recursive: true });
108
+ for (const page of pages) writeFileSync(join(section, `${page}.mdx`), "body\n");
109
+ writeFileSync(join(section, "_sidebar.json"), JSON.stringify({ group, hidden }));
110
+ const lex = join(dir, "lexicons", "fixture");
111
+ mkdirSync(join(lex, "docs", "pages"), { recursive: true });
112
+ return lex;
113
+ }
114
+
115
+ test("every page in the fragment, or hidden by it, is reachable", () => {
116
+ const lex = repoWithSection(
117
+ ["index", "start", "secret"],
118
+ {
119
+ label: "Fixture",
120
+ items: [
121
+ { label: "Overview", slug: "lexicons/fixture" },
122
+ { label: "Tutorials", items: [{ label: "Start", slug: "lexicons/fixture/start" }] },
123
+ ],
124
+ },
125
+ ["lexicons/fixture/secret"],
126
+ );
127
+ expect(auditDocsReachability(lex)).toEqual({ hasSite: true, pages: ["index", "secret", "start"], unreachable: [] });
128
+ });
129
+
130
+ test("a page the fragment does not name is unreachable", () => {
131
+ const lex = repoWithSection(["index", "orphan"], { label: "Fixture", items: [{ label: "Overview", slug: "lexicons/fixture" }] });
132
+ expect(auditDocsReachability(lex).unreachable).toEqual(["orphan"]);
133
+ });
134
+
135
+ test("before the section is generated there is nothing to audit", () => {
136
+ mainSite();
137
+ const lex = join(dir, "lexicons", "fixture");
138
+ mkdirSync(lex, { recursive: true });
139
+ expect(auditDocsReachability(lex).hasSite).toBe(false);
140
+ });
141
+ });
@@ -15,7 +15,10 @@
15
15
  */
16
16
 
17
17
  import { existsSync, readdirSync, readFileSync } from "fs";
18
- import { join } from "path";
18
+ import { basename, join, resolve } from "path";
19
+
20
+ import { collectSidebarSlugs } from "../../codegen/docs";
21
+ import { findChantDocsSite, sectionContentDir } from "../../codegen/docs-in-site";
19
22
 
20
23
  export interface DocsReachability {
21
24
  /** Whether this lexicon ships a Starlight site at all. */
@@ -55,7 +58,7 @@ export function auditDocsReachability(lexiconDir: string): DocsReachability {
55
58
  const configPath = join(lexiconDir, "docs", "astro.config.mjs");
56
59
 
57
60
  if (!existsSync(contentDir) || !existsSync(configPath)) {
58
- return { hasSite: false, pages: [], unreachable: [] };
61
+ return auditSectionReachability(lexiconDir);
59
62
  }
60
63
 
61
64
  const slugs = sidebarSlugs(readFileSync(configPath, "utf-8"));
@@ -99,3 +102,37 @@ export function auditDocsClassification(lexiconDir: string): DocsClassification
99
102
  }
100
103
  return { hasPages: true, unclassified };
101
104
  }
105
+
106
+ /**
107
+ * Reachability for a lexicon whose docs are a section of the chant main site
108
+ * (chant #3993): its generated pages under
109
+ * `docs/src/content/docs/lexicons/<name>/` against the `_sidebar.json` the
110
+ * pipeline wrote beside them. A page the fragment lists as hidden is
111
+ * deliberately URL-only and does not count. Before the section has been
112
+ * generated there is nothing to audit, which reads as no site, like a
113
+ * lexicon with no docs.
114
+ */
115
+ function auditSectionReachability(lexiconDir: string): DocsReachability {
116
+ const site = findChantDocsSite(lexiconDir);
117
+ const name = basename(resolve(lexiconDir));
118
+ const dir = site ? sectionContentDir(site, name) : undefined;
119
+ const fragmentPath = dir ? join(dir, "_sidebar.json") : undefined;
120
+ if (!dir || !fragmentPath || !existsSync(fragmentPath)) return { hasSite: false, pages: [], unreachable: [] };
121
+
122
+ const fragment = JSON.parse(readFileSync(fragmentPath, "utf-8")) as {
123
+ group: Record<string, unknown>;
124
+ hidden?: string[];
125
+ };
126
+ const prefix = `lexicons/${name}/`;
127
+ const reached = new Set(
128
+ [...collectSidebarSlugs([fragment.group]), ...(fragment.hidden ?? [])].map((slug) =>
129
+ slug.startsWith(prefix) ? slug.slice(prefix.length) : slug,
130
+ ),
131
+ );
132
+ const pages = readdirSync(dir)
133
+ .filter((f) => f.endsWith(".mdx") || f.endsWith(".md"))
134
+ .map((f) => f.replace(/\.mdx?$/, ""))
135
+ .sort();
136
+ const unreachable = pages.filter((slug) => slug !== "index" && !reached.has(slug));
137
+ return { hasSite: true, pages, unreachable };
138
+ }