@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,351 @@
1
+ /**
2
+ * Test stand-ins for the publish targets (#4047): an OCI registry fake that
3
+ * listens on 127.0.0.1 inside the test process and answers with a Bearer
4
+ * challenge as GHCR and Docker Hub do, an OCI image layout writer, a fake
5
+ * `npm` that records each call and keeps a registry with dist-tags in a file,
6
+ * and a forge fake with draft releases (#4048). Nothing here reaches a network or a real
7
+ * registry, and every server a test starts is closed by it.
8
+ */
9
+
10
+ import { createHash } from "node:crypto";
11
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
12
+ import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
13
+ import type { AddressInfo } from "node:net";
14
+ import { join } from "node:path";
15
+
16
+ const sha = (b: Uint8Array | string) => `sha256:${createHash("sha256").update(b).digest("hex")}`;
17
+
18
+ export interface FakeRegistry {
19
+ /** `127.0.0.1:<port>`, for a repository reference. */
20
+ host: string;
21
+ blobs: Map<string, Buffer>;
22
+ manifests: Map<string, { name: string; type: string; bytes: Buffer }>;
23
+ /** Each request: method, path and the authorization header it carried. */
24
+ requests: Array<{ method: string; path: string; authorization: string | null }>;
25
+ uploads: number;
26
+ close(): Promise<void>;
27
+ }
28
+
29
+ /** A registry fake with a Bearer token service that accepts `username` and `password`. */
30
+ export async function startFakeRegistry(creds: { username: string; password: string; token: string }): Promise<FakeRegistry> {
31
+ const blobs = new Map<string, Buffer>();
32
+ const manifests = new Map<string, { name: string; type: string; bytes: Buffer }>();
33
+ const requests: FakeRegistry["requests"] = [];
34
+ const state = { uploads: 0, port: 0 };
35
+ const read = (req: IncomingMessage) =>
36
+ new Promise<Buffer>((res) => {
37
+ const parts: Buffer[] = [];
38
+ req.on("data", (c: Buffer) => parts.push(c));
39
+ req.on("end", () => res(Buffer.concat(parts)));
40
+ });
41
+ const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
42
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${state.port}`);
43
+ const body = await read(req);
44
+ requests.push({ method: req.method ?? "", path: url.pathname, authorization: req.headers.authorization ?? null });
45
+ const send = (status: number, headers: Record<string, string> = {}, text = "") => {
46
+ res.writeHead(status, headers);
47
+ res.end(text);
48
+ };
49
+ if (url.pathname === "/token") {
50
+ const basic = `Basic ${Buffer.from(`${creds.username}:${creds.password}`).toString("base64")}`;
51
+ if (req.headers.authorization !== basic) return send(401, {}, "bad credentials");
52
+ return send(200, { "content-type": "application/json" }, JSON.stringify({ token: creds.token }));
53
+ }
54
+ if (req.headers.authorization !== `Bearer ${creds.token}`) {
55
+ return send(401, { "www-authenticate": `Bearer realm="http://127.0.0.1:${state.port}/token",service="fake-registry"` }, "unauthorized");
56
+ }
57
+ let m = url.pathname.match(/^\/v2\/(.+)\/blobs\/uploads\/(.*)$/);
58
+ if (m) {
59
+ if (req.method === "POST") return send(202, { location: `/v2/${m[1]}/blobs/uploads/u${requests.length}?_state=s` });
60
+ const digest = url.searchParams.get("digest") ?? "";
61
+ if (req.method === "PUT" && sha(body) === digest && url.searchParams.get("_state") === "s") {
62
+ blobs.set(digest, body);
63
+ state.uploads++;
64
+ return send(201, { "docker-content-digest": digest });
65
+ }
66
+ return send(400, {}, "digest invalid");
67
+ }
68
+ m = url.pathname.match(/^\/v2\/(.+)\/blobs\/(sha256:[0-9a-f]+)$/);
69
+ if (m && req.method === "HEAD") return send(blobs.has(m[2]!) ? 200 : 404);
70
+ m = url.pathname.match(/^\/v2\/(.+)\/manifests\/(.+)$/);
71
+ if (m && (req.method === "HEAD" || req.method === "GET")) {
72
+ const found = manifests.get(m[2]!);
73
+ return found ? send(200, { "content-type": found.type, "docker-content-digest": sha(found.bytes) }, req.method === "GET" ? found.bytes.toString("utf-8") : "") : send(404);
74
+ }
75
+ if (m && req.method === "PUT") {
76
+ const ref = m[2]!;
77
+ if (ref.startsWith("sha256:") && sha(body) !== ref) return send(400, {}, "manifest digest invalid");
78
+ const parsed = JSON.parse(body.toString("utf-8")) as { config?: { digest: string }; layers?: Array<{ digest: string }> };
79
+ for (const d of [parsed.config, ...(parsed.layers ?? [])]) if (d && !blobs.has(d.digest)) return send(400, {}, `blob unknown ${d.digest}`);
80
+ manifests.set(ref, { name: m[1]!, type: String(req.headers["content-type"]), bytes: body });
81
+ return send(201, { "docker-content-digest": sha(body) });
82
+ }
83
+ return send(404, {}, "not found");
84
+ });
85
+ await new Promise<void>((r) => server.listen(0, "127.0.0.1", () => r()));
86
+ state.port = (server.address() as AddressInfo).port;
87
+ return {
88
+ host: `127.0.0.1:${state.port}`,
89
+ blobs,
90
+ manifests,
91
+ requests,
92
+ get uploads() {
93
+ return state.uploads;
94
+ },
95
+ close: () => new Promise<void>((r) => server.close(() => r())),
96
+ };
97
+ }
98
+
99
+ /** Write a one-image OCI layout at `dir`, returning the manifest's and the config's digests. */
100
+ export function writeOciLayout(dir: string, content = "layer bytes"): { digest: string; configDigest: string } {
101
+ const blobsDir = join(dir, "blobs", "sha256");
102
+ mkdirSync(blobsDir, { recursive: true });
103
+ const put = (bytes: string) => {
104
+ const d = sha(bytes);
105
+ writeFileSync(join(blobsDir, d.slice(7)), bytes);
106
+ return { digest: d, size: Buffer.byteLength(bytes) };
107
+ };
108
+ const layer = put(content);
109
+ const config = put(JSON.stringify({ architecture: "amd64", os: "linux", rootfs: { type: "layers", diff_ids: [layer.digest] } }));
110
+ const manifest = put(
111
+ JSON.stringify({
112
+ schemaVersion: 2,
113
+ mediaType: "application/vnd.oci.image.manifest.v1+json",
114
+ config: { mediaType: "application/vnd.oci.image.config.v1+json", ...config },
115
+ layers: [{ mediaType: "application/vnd.oci.image.layer.v1.tar", ...layer }],
116
+ }),
117
+ );
118
+ writeFileSync(join(dir, "oci-layout"), JSON.stringify({ imageLayoutVersion: "1.0.0" }));
119
+ writeFileSync(join(dir, "index.json"), JSON.stringify({ schemaVersion: 2, manifests: [{ mediaType: "application/vnd.oci.image.manifest.v1+json", ...manifest }] }));
120
+ return { digest: manifest.digest, configDigest: config.digest };
121
+ }
122
+
123
+ /** What the fake npm's registry holds: each package's versions and dist-tags. */
124
+ export interface FakeNpmRegistry {
125
+ packages: Record<string, { versions: string[]; distTags: Record<string, string> }>;
126
+ }
127
+
128
+ /** The registry file a fake npm writing calls to `log` keeps its state in. */
129
+ export const fakeNpmRegistryFile = (log: string) => `${log}.registry.json`;
130
+
131
+ /** Read the fake npm's registry. */
132
+ export function readFakeNpmRegistry(log: string): FakeNpmRegistry {
133
+ const file = fakeNpmRegistryFile(log);
134
+ return existsSync(file) ? (JSON.parse(readFileSync(file, "utf-8")) as FakeNpmRegistry) : { packages: {} };
135
+ }
136
+
137
+ /** Seed the fake npm's registry, such as with an earlier version as `latest`. */
138
+ export function seedFakeNpmRegistry(log: string, registry: FakeNpmRegistry): void {
139
+ writeFileSync(fakeNpmRegistryFile(log), JSON.stringify(registry));
140
+ }
141
+
142
+ /**
143
+ * Write a fake `npm` at `<binDir>/npm` that appends `{ args, tokenSet }` to
144
+ * `log` for each call and prints the token it was given (so a test can show
145
+ * it is masked). It keeps a registry in `<log>.registry.json`, as npm's does:
146
+ *
147
+ * - `publish <dir|tgz> [--tag <t>]` adds the version under `<t>` (default
148
+ * `latest`), and refuses a version already there with a 403. As npm's
149
+ * registry does, a package's first version is also `latest`. It exits 1
150
+ * when the package path contains `$FAKE_NPM_FAIL`.
151
+ * - `view <name>@<version> version` prints the version when it is there.
152
+ * - `dist-tag add <name>@<version> <tag>` points the tag at a version that is
153
+ * there. It exits 1 when the name contains `$FAKE_NPM_FAIL_TAG`.
154
+ * - `install` (#4049) checks that every dependency in `./package.json` is in
155
+ * its registry at that version, and installs nothing.
156
+ * - `audit signatures` (#4049) prints what npm prints for verified
157
+ * signatures and attestations, and exits 1 when `$FAKE_NPM_AUDIT_FAIL` is set.
158
+ */
159
+ export function writeFakeNpm(binDir: string, log: string): string {
160
+ mkdirSync(binDir, { recursive: true });
161
+ const file = join(binDir, "npm");
162
+ writeFileSync(
163
+ file,
164
+ `#!${process.execPath}
165
+ const fs = require("fs");
166
+ const path = require("path");
167
+ const { execFileSync } = require("child_process");
168
+ const args = process.argv.slice(2);
169
+ const token = process.env.NODE_AUTH_TOKEN || "";
170
+ const regFile = ${JSON.stringify(log)} + ".registry.json";
171
+ fs.appendFileSync(${JSON.stringify(log)}, JSON.stringify({ args, tokenSet: token !== "" }) + "\\n");
172
+ console.log("npm notice using //registry.example/:_authToken=" + token);
173
+ console.error("npm notice token " + token);
174
+ const reg = fs.existsSync(regFile) ? JSON.parse(fs.readFileSync(regFile, "utf-8")) : { packages: {} };
175
+ const save = () => fs.writeFileSync(regFile, JSON.stringify(reg));
176
+ const flag = (name) => { const i = args.indexOf(name); return i < 0 ? null : args[i + 1]; };
177
+ const split = (spec) => { const at = spec.lastIndexOf("@"); return [spec.slice(0, at), spec.slice(at + 1)]; };
178
+ if (args[0] === "publish") {
179
+ if (process.env.FAKE_NPM_FAIL && args[1] && args[1].includes(process.env.FAKE_NPM_FAIL)) { console.error("npm error 403 Forbidden"); process.exit(1); }
180
+ const text = fs.statSync(args[1]).isDirectory() ? fs.readFileSync(path.join(args[1], "package.json"), "utf-8") : execFileSync("tar", ["-xzOf", args[1], "package/package.json"], { encoding: "utf-8" });
181
+ const { name, version } = JSON.parse(text);
182
+ const p = reg.packages[name] || (reg.packages[name] = { versions: [], distTags: {} });
183
+ if (p.versions.includes(version)) { console.error("npm error 403 You cannot publish over the previously published versions: " + version); process.exit(1); }
184
+ p.versions.push(version);
185
+ p.distTags[flag("--tag") || "latest"] = version;
186
+ if (!p.distTags.latest) p.distTags.latest = version;
187
+ save();
188
+ console.log("+ " + name + "@" + version);
189
+ } else if (args[0] === "view") {
190
+ const [name, version] = split(args[1]);
191
+ const p = reg.packages[name];
192
+ if (!p) { console.error("npm error 404 Not Found - " + name); process.exit(1); }
193
+ if (p.versions.includes(version)) console.log(version);
194
+ } else if (args[0] === "dist-tag" && args[1] === "add") {
195
+ const [name, version] = split(args[2]);
196
+ if (process.env.FAKE_NPM_FAIL_TAG && name.includes(process.env.FAKE_NPM_FAIL_TAG)) { console.error("npm error 403 Forbidden - no permission to change dist-tags"); process.exit(1); }
197
+ const p = reg.packages[name];
198
+ if (!p || !p.versions.includes(version)) { console.error("npm error 404 " + args[2] + " is not in the npm registry"); process.exit(1); }
199
+ p.distTags[args[3]] = version;
200
+ save();
201
+ console.log("+" + args[3] + ": " + args[2]);
202
+ } else if (args[0] === "install") {
203
+ const deps = JSON.parse(fs.readFileSync("package.json", "utf-8")).dependencies || {};
204
+ for (const [name, version] of Object.entries(deps)) {
205
+ const p = reg.packages[name];
206
+ if (!p || !p.versions.includes(version)) { console.error("npm error 404 " + name + "@" + version + " is not in this registry"); process.exit(1); }
207
+ }
208
+ console.log("added " + Object.keys(deps).length + " packages");
209
+ } else if (args[0] === "audit" && args[1] === "signatures") {
210
+ const n = Object.keys(JSON.parse(fs.readFileSync("package.json", "utf-8")).dependencies || {}).length;
211
+ if (process.env.FAKE_NPM_AUDIT_FAIL) { console.error(n + " packages have invalid attestations"); process.exit(1); }
212
+ console.log("audited " + n + " packages");
213
+ console.log(n + " packages have verified registry signatures");
214
+ console.log(n + " packages have verified attestations");
215
+ } else {
216
+ console.error("fake npm: unknown command " + args.join(" ")); process.exit(2);
217
+ }
218
+ `,
219
+ );
220
+ chmodSync(file, 0o755);
221
+ return file;
222
+ }
223
+
224
+ /** A forge release as {@link startFakeForge} keeps it. */
225
+ export interface FakeForgeRelease {
226
+ id: number;
227
+ tag_name: string;
228
+ draft: boolean;
229
+ target: string;
230
+ assets: Array<{ name: string; digest: string | null; bytes: string }>;
231
+ }
232
+
233
+ /** A forge fake answering through `fetch`: GitHub or Forgejo with draft releases, or GitLab with releases, links and a generic package registry. */
234
+ export interface FakeForge {
235
+ fetch: (url: string, init: { method: string; headers: Record<string, string>; body?: Uint8Array | string }) => Promise<{ ok: boolean; status: number; headers: { get(name: string): string | null }; text(): Promise<string> }>;
236
+ calls: Array<{ method: string; url: string; headers: Record<string, string>; body?: Uint8Array | string }>;
237
+ releases: FakeForgeRelease[];
238
+ /** GitLab: the package registry's files, by URL. */
239
+ packages: Map<string, string>;
240
+ /** The releases a consumer sees: published (not draft) ones, with their asset names. */
241
+ visible(): Array<{ tag: string; assets: string[] }>;
242
+ /** Make the next request whose `METHOD url` starts with `prefix` answer 500. */
243
+ failOnce(prefix: string): void;
244
+ }
245
+
246
+ /** A forge fake at `api` for `repo`, which holds `releases` to begin with. */
247
+ export function startFakeForge(forge: "github" | "forgejo" | "gitlab", api: string, repo: string, releases: FakeForgeRelease[] = []): FakeForge {
248
+ const calls: FakeForge["calls"] = [];
249
+ const packages = new Map<string, string>();
250
+ const failures: string[] = [];
251
+ let nextId = 100 + releases.length;
252
+ const root = forge === "gitlab" ? `${api}/projects/${encodeURIComponent(repo)}` : `${api}/repos/${repo}`;
253
+ const uploads = forge === "github" ? "https://uploads.forge.test" : api;
254
+ const json = (status: number, body: unknown) => ({ ok: status < 300, status, headers: { get: () => null }, text: async () => (body === undefined ? "" : JSON.stringify(body)) });
255
+ const view = (r: FakeForgeRelease) =>
256
+ forge === "gitlab"
257
+ ? { tag_name: r.tag_name, assets: { links: r.assets.map((a) => ({ name: a.name })) } }
258
+ : { id: r.id, tag_name: r.tag_name, draft: r.draft, upload_url: `${uploads}/repos/${repo}/releases/${r.id}/assets{?name,label}`, assets: r.assets.map((a) => ({ name: a.name, ...(forge === "github" ? { digest: a.digest } : {}) })) };
259
+ const text = (b: Uint8Array | string | undefined) => (b === undefined ? "" : typeof b === "string" ? b : Buffer.from(b).toString("utf-8"));
260
+ const fetch: FakeForge["fetch"] = async (url, init) => {
261
+ calls.push({ method: init.method, url, headers: init.headers, ...(init.body !== undefined ? { body: init.body } : {}) });
262
+ const key = `${init.method} ${url}`;
263
+ const f = failures.findIndex((p) => key.startsWith(p));
264
+ if (f >= 0) {
265
+ failures.splice(f, 1);
266
+ return json(500, { message: "Internal Server Error" });
267
+ }
268
+ const u = new URL(url);
269
+ const path = url.split("?")[0]!;
270
+ if (forge === "gitlab") {
271
+ if (init.method === "PUT" && path.startsWith(`${root}/packages/generic/`)) {
272
+ packages.set(path, text(init.body));
273
+ return json(201, { message: "201 Created" });
274
+ }
275
+ const byTag = path.startsWith(`${root}/releases/`) ? decodeURIComponent(path.slice(`${root}/releases/`.length)) : null;
276
+ if (init.method === "GET" && byTag !== null) {
277
+ const r = releases.find((x) => x.tag_name === byTag);
278
+ return r ? json(200, view(r)) : json(404, { message: "404 Not Found" });
279
+ }
280
+ if (init.method === "POST" && path === `${root}/releases`) {
281
+ const b = JSON.parse(text(init.body)) as { tag_name: string; ref: string; assets?: { links?: Array<{ name: string; url: string }> } };
282
+ if (releases.some((x) => x.tag_name === b.tag_name)) return json(409, { message: "Release already exists" });
283
+ const r: FakeForgeRelease = { id: nextId++, tag_name: b.tag_name, draft: false, target: b.ref, assets: (b.assets?.links ?? []).map((l) => ({ name: l.name, digest: null, bytes: packages.get(l.url) ?? "" })) };
284
+ releases.push(r);
285
+ return json(201, view(r));
286
+ }
287
+ return json(404, { message: "404 Not Found" });
288
+ }
289
+ if (init.method === "GET" && path.startsWith(`${root}/releases/tags/`)) {
290
+ const tag = decodeURIComponent(path.slice(`${root}/releases/tags/`.length));
291
+ const r = releases.find((x) => x.tag_name === tag && !x.draft);
292
+ return r ? json(200, view(r)) : json(404, { message: "Not Found" });
293
+ }
294
+ if (init.method === "GET" && path === `${root}/releases`) return json(200, releases.map(view));
295
+ if (init.method === "POST" && path === `${root}/releases`) {
296
+ const b = JSON.parse(text(init.body)) as { tag_name: string; target_commitish: string; draft?: boolean };
297
+ const r: FakeForgeRelease = { id: nextId++, tag_name: b.tag_name, draft: b.draft === true, target: b.target_commitish, assets: [] };
298
+ releases.push(r);
299
+ return json(201, view(r));
300
+ }
301
+ const asset = path.match(/\/releases\/(\d+)\/assets$/);
302
+ if (init.method === "POST" && asset && (path.startsWith(uploads) || path.startsWith(root))) {
303
+ const r = releases.find((x) => x.id === Number(asset[1]));
304
+ const name = u.searchParams.get("name") ?? "";
305
+ if (!r) return json(404, { message: "Not Found" });
306
+ if (r.assets.some((a) => a.name === name)) return json(422, { message: "already_exists" });
307
+ let bytes = text(init.body);
308
+ if (forge === "forgejo") bytes = bytes.split("\r\n\r\n")[1]?.split("\r\n--")[0] ?? "";
309
+ r.assets.push({ name, digest: sha(bytes), bytes });
310
+ return json(201, { name, browser_download_url: `https://forge.test/${repo}/releases/download/${r.tag_name}/${name}` });
311
+ }
312
+ const edit = path.match(/\/releases\/(\d+)$/);
313
+ if (init.method === "PATCH" && edit) {
314
+ const r = releases.find((x) => x.id === Number(edit[1]));
315
+ if (!r) return json(404, { message: "Not Found" });
316
+ const b = JSON.parse(text(init.body)) as { draft?: boolean };
317
+ if (b.draft === false) r.draft = false;
318
+ return json(200, view(r));
319
+ }
320
+ return json(404, { message: "Not Found" });
321
+ };
322
+ return {
323
+ fetch,
324
+ calls,
325
+ releases,
326
+ packages,
327
+ visible: () => releases.filter((r) => !r.draft).map((r) => ({ tag: r.tag_name, assets: r.assets.map((a) => a.name) })),
328
+ failOnce: (prefix) => void failures.push(prefix),
329
+ };
330
+ }
331
+
332
+ /** A small SPDX document, as `npm sbom --sbom-format spdx` writes one. */
333
+ export function spdxDocument(name: string): string {
334
+ return `${JSON.stringify({ spdxVersion: "SPDX-2.3", dataLicense: "CC0-1.0", SPDXID: "SPDXRef-DOCUMENT", name, documentNamespace: `https://example.test/${name}`, creationInfo: { created: "2026-10-17T00:00:00Z", creators: ["Tool: npm"] }, packages: [] }, null, 2)}\n`;
335
+ }
336
+
337
+ /** Write an npm package directory, with an `sbom.spdx.json` at its root unless `sbom` is false (#4049). */
338
+ export function writePackage(dir: string, name: string, version: string, sbom = true): void {
339
+ mkdirSync(dir, { recursive: true });
340
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name, version }, null, 2));
341
+ if (sbom) writeFileSync(join(dir, "sbom.spdx.json"), spdxDocument(name));
342
+ }
343
+
344
+ /** Write a `package-lock.json` at `dir`, which the OCI and forge targets write their SBOM from (#4049). */
345
+ export function writeLockfile(dir: string): void {
346
+ mkdirSync(dir, { recursive: true });
347
+ writeFileSync(
348
+ join(dir, "package-lock.json"),
349
+ JSON.stringify({ name: "acme", lockfileVersion: 3, packages: { "": { name: "acme" }, "node_modules/left-pad": { version: "1.3.0" } } }, null, 2),
350
+ );
351
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Stored change sets (#4022) against temp git repos: the store, the gate's
3
+ * call that maps and stores, `chant change-set show`'s document and the link
4
+ * a gate in `workspace status` carries. Graph reads are injected here;
5
+ * `gate-change-set.e2e.test.ts` reads a real one.
6
+ */
7
+
8
+ import { describe, expect, test } from "vitest";
9
+ import { withTestDir } from "@intentius/chant-test-utils";
10
+ import { spawnSync } from "node:child_process";
11
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { composeChangeSet, lifecyclePlanPart, type ChangeSetDocument } from "../change-set";
14
+ import type { ChangeSet as LifecycleChangeSet } from "./change-set";
15
+ import { CHANGE_SETS_DIR, changeSetFile, ChangeSetStoreError, persistChangeSet, readStoredChangeSet } from "./change-set-store";
16
+ import { storeGateChangeSet } from "./gate-change-set";
17
+ import { appendPendingGate } from "./gate-ledger";
18
+ import { writeLedgerFiles } from "./git";
19
+ import { clearMemberLedgerCache } from "./member-ledger";
20
+ import { changeSetShowDocument } from "../workspace/change-set-show";
21
+ import { readMemberGates } from "../workspace/status-gates";
22
+ import { loadAjv2020 } from "../workspace/ajv2020";
23
+ import showSchema from "../workspace/change-set-show.schema.json";
24
+ import changeSetSchema from "../workspace/change-set.schema.json";
25
+
26
+ const plan = JSON.parse(readFileSync(join(import.meta.dirname, "..", "__fixtures__", "change-set", "lifecycle-plan.floci.json"), "utf8")) as LifecycleChangeSet;
27
+ const doc = (): ChangeSetDocument => composeChangeSet([lifecyclePlanPart({ member: "queues", plan })]);
28
+
29
+ function git(dir: string, ...args: string[]): string {
30
+ const r = spawnSync("git", args, { cwd: dir, encoding: "utf-8" });
31
+ if (r.status !== 0) throw new Error(`git ${args.join(" ")}: ${r.stderr}`);
32
+ return r.stdout.trim();
33
+ }
34
+
35
+ function initRepo(dir: string): string {
36
+ git(dir, "init", "-q", "-b", "main");
37
+ git(dir, "config", "user.email", "test@chant.dev");
38
+ git(dir, "config", "user.name", "Test");
39
+ writeFileSync(join(dir, "README.md"), "fixture\n");
40
+ git(dir, "add", ".");
41
+ git(dir, "commit", "-q", "-m", "init");
42
+ return git(dir, "rev-parse", "HEAD");
43
+ }
44
+
45
+ function validator(schema: object) {
46
+ const Ajv = loadAjv2020();
47
+ const ajv = new Ajv({ strict: false, allErrors: true }) as unknown as { compile(s: object): ((d: unknown) => boolean) & { errors?: unknown } };
48
+ return ajv.compile(schema);
49
+ }
50
+
51
+ describe("persistChangeSet and readStoredChangeSet", () => {
52
+ test("stores a document under its digest once, and reads it back", async () => {
53
+ await withTestDir(async (dir) => {
54
+ initRepo(dir);
55
+ const d = doc();
56
+ expect(await readStoredChangeSet(d.digest, { cwd: dir })).toEqual({ status: "missing" });
57
+ const first = await persistChangeSet(d, { cwd: dir });
58
+ expect(first.written).toBe(true);
59
+ expect(git(dir, "ls-tree", "--name-only", `chant/lifecycle:${CHANGE_SETS_DIR}`)).toBe(changeSetFile(d.digest));
60
+ expect(changeSetFile(d.digest)).not.toContain(":");
61
+ const again = await persistChangeSet(d, { cwd: dir });
62
+ expect(again).toEqual({ commit: null, written: false });
63
+ const read = await readStoredChangeSet(d.digest, { cwd: dir });
64
+ expect(read).toEqual({ status: "found", document: JSON.parse(JSON.stringify(d)) });
65
+ });
66
+ });
67
+
68
+ test("refuses a document that does not hash to its digest", async () => {
69
+ await withTestDir(async (dir) => {
70
+ initRepo(dir);
71
+ const d = doc();
72
+ const forged = { ...d, entries: d.entries.slice(1) };
73
+ await expect(persistChangeSet(forged, { cwd: dir })).rejects.toBeInstanceOf(ChangeSetStoreError);
74
+ await expect(persistChangeSet({ ...d, $schema: "x" } as never, { cwd: dir })).rejects.toBeInstanceOf(ChangeSetStoreError);
75
+ });
76
+ });
77
+
78
+ test("a file under a digest that is not that document reads as malformed", async () => {
79
+ await withTestDir(async (dir) => {
80
+ initRepo(dir);
81
+ const d = doc();
82
+ const other = { ...d, entries: d.entries.slice(1) };
83
+ await writeLedgerFiles([{ path: `${CHANGE_SETS_DIR}/${changeSetFile(d.digest)}`, content: JSON.stringify(other), expectPriorSha: null }], "forged", { cwd: dir });
84
+ const read = await readStoredChangeSet(d.digest, { cwd: dir });
85
+ expect(read.status).toBe("malformed");
86
+ });
87
+ });
88
+ });
89
+
90
+ describe("storeGateChangeSet", () => {
91
+ test("maps the entries onto the graph it reads, and stores in the member's ledger", async () => {
92
+ await withTestDir(async (dir) => {
93
+ initRepo(dir);
94
+ writeFileSync(join(dir, "chant.workspace.json"), JSON.stringify({ name: "acme", schema: 1, members: [{ name: "delivery", dir: "delivery", kind: "chant" }] }));
95
+ mkdirSync(join(dir, "delivery"));
96
+ clearMemberLedgerCache();
97
+ const d = doc();
98
+ const reads: string[] = [];
99
+ const stored = await storeGateChangeSet({
100
+ doc: d,
101
+ cwd: join(dir, "delivery"),
102
+ at: "c".repeat(40),
103
+ readGraph: async (at) => {
104
+ reads.push(at);
105
+ return { ids: ["jobs", "events", "dlq"] };
106
+ },
107
+ });
108
+ expect(stored).toEqual({ digest: d.digest, written: true, graph: { member: "delivery", at: "c".repeat(40) } });
109
+ expect(reads).toEqual([join(dir, "delivery")]);
110
+ expect(git(dir, "ls-tree", "-r", "--name-only", "chant/lifecycle")).toBe(`_members/delivery/${CHANGE_SETS_DIR}/${changeSetFile(d.digest)}`);
111
+
112
+ // Stored already: the graph is not read again.
113
+ expect(await storeGateChangeSet({ doc: d, cwd: join(dir, "delivery"), readGraph: async () => { throw new Error("read again"); } })).toEqual({ digest: d.digest, written: false });
114
+
115
+ // chant change-set show, from the workspace root with --member, and from the member's directory.
116
+ const shown = await changeSetShowDocument({ cwd: dir, digest: d.digest, member: ["delivery"] });
117
+ expect(validator(showSchema)(shown)).toBe(true);
118
+ if ("error" in shown) throw new Error(shown.error.message);
119
+ expect(shown.member).toBe("delivery");
120
+ expect(shown.reason).toBeNull();
121
+ expect(validator(changeSetSchema)(shown.changeSet)).toBe(true);
122
+ expect(shown.changeSet!.entries.map((e) => [e.address, e.node])).toEqual([
123
+ ["alerts", null],
124
+ ["dlq", "delivery/dlq"],
125
+ ["events", "delivery/events"],
126
+ ["jobs", "delivery/jobs"],
127
+ ]);
128
+ const fromMember = await changeSetShowDocument({ cwd: join(dir, "delivery"), digest: d.digest });
129
+ expect(fromMember).toEqual(shown);
130
+ // The flat ledger holds nothing under that digest.
131
+ const flat = await changeSetShowDocument({ cwd: dir, digest: d.digest });
132
+ expect(flat).toMatchObject({ member: null, changeSet: null, reason: null });
133
+ expect(validator(showSchema)(flat)).toBe(true);
134
+ });
135
+ });
136
+
137
+ test("a graph that can't be read stores the document with null nodes and says why", async () => {
138
+ await withTestDir(async (dir) => {
139
+ initRepo(dir);
140
+ clearMemberLedgerCache();
141
+ const d = doc();
142
+ const stored = await storeGateChangeSet({ doc: d, cwd: dir, readGraph: async () => ({ error: "chant graph --format ir exited 1" }) });
143
+ expect(stored.written).toBe(true);
144
+ expect(stored.graph).toBeNull();
145
+ expect(stored.warning).toContain("exited 1");
146
+ const read = await readStoredChangeSet(d.digest, { cwd: dir });
147
+ if (read.status !== "found") throw new Error(read.status);
148
+ expect(read.document.graph).toBeNull();
149
+ expect(read.document.entries.every((e) => e.node === null)).toBe(true);
150
+ });
151
+ });
152
+
153
+ test("show reports a malformed file and a checkout with no git", async () => {
154
+ await withTestDir(async (dir) => {
155
+ const outside = await changeSetShowDocument({ cwd: dir, digest: doc().digest });
156
+ expect(outside).toMatchObject({ error: { code: "not-a-git-repository" } });
157
+ expect(validator(showSchema)(outside)).toBe(true);
158
+ initRepo(dir);
159
+ clearMemberLedgerCache();
160
+ const d = doc();
161
+ await writeLedgerFiles([{ path: `${CHANGE_SETS_DIR}/${changeSetFile(d.digest)}`, content: "{", expectPriorSha: null }], "broken", { cwd: dir });
162
+ const shown = await changeSetShowDocument({ cwd: dir, digest: d.digest });
163
+ expect(shown).toMatchObject({ changeSet: null, reason: { code: "ledger-malformed" } });
164
+ expect(validator(showSchema)(shown)).toBe(true);
165
+ });
166
+ });
167
+ });
168
+
169
+ describe("a gate in workspace status links to its stored change set", () => {
170
+ test("a gate whose planDigest names a stored change set carries changeSet; a component gate does not", async () => {
171
+ await withTestDir(async (dir) => {
172
+ initRepo(dir);
173
+ clearMemberLedgerCache();
174
+ const d = doc();
175
+ await storeGateChangeSet({ doc: d, cwd: dir, readGraph: async () => ({ ids: ["jobs"] }) });
176
+ const now = "2026-10-16T00:00:00.000Z";
177
+ const later = "2026-10-17T00:00:00.000Z";
178
+ await appendPendingGate({ op: "pr-7", gate: "pr-apply", planDigest: d.digest, timestamp: now, expiresAt: later }, { cwd: dir });
179
+ const lifecyclePlan = "jcs1-sha256:" + "0".repeat(64);
180
+ await appendPendingGate({ op: "api", gate: "prod-approval", planDigest: lifecyclePlan, environment: "prod", timestamp: now, expiresAt: later }, { cwd: dir });
181
+ const commit = git(dir, "rev-parse", "chant/lifecycle");
182
+ const { gates } = await readMemberGates("_gates", "flat", commit, ["prod"], dir, now);
183
+ const byOp = Object.fromEntries(gates.map((g) => [g.component, g]));
184
+ expect(byOp["pr-7"]).toMatchObject({ state: "pending", planDigest: d.digest, changeSet: d.digest });
185
+ expect(byOp.api.planDigest).toBe(lifecyclePlan);
186
+ expect(byOp.api).not.toHaveProperty("changeSet");
187
+ });
188
+ });
189
+ });
190
+
191
+ describe("a release gate's stored change set (#4045)", () => {
192
+ test("names the release and environment it was planned for, and a release gate in status names its release", async () => {
193
+ await withTestDir(async (dir) => {
194
+ initRepo(dir);
195
+ clearMemberLedgerCache();
196
+ const releaseDigest = "jcs1-sha256:" + "a".repeat(64);
197
+ const d = composeChangeSet([lifecyclePlanPart({ member: "queues", plan })], { planned: { releaseDigest, environment: "prod" } });
198
+ await storeGateChangeSet({ doc: d, cwd: dir, readGraph: async () => ({ ids: ["jobs"] }) });
199
+ const read = await readStoredChangeSet(d.digest, { cwd: dir });
200
+ if (read.status !== "found") throw new Error(read.status);
201
+ expect(read.document).toMatchObject({ releaseDigest, environment: "prod" });
202
+ expect(validator(changeSetSchema)(read.document)).toBe(true);
203
+ const shown = await changeSetShowDocument({ cwd: dir, digest: d.digest });
204
+ expect(shown).toMatchObject({ changeSet: { releaseDigest, environment: "prod" } });
205
+ expect(validator(showSchema)(shown)).toBe(true);
206
+
207
+ const now = "2026-10-16T00:00:00.000Z";
208
+ await appendPendingGate(
209
+ { op: "release", gate: "prod-release", planDigest: d.digest, environment: "prod", releaseDigest, timestamp: now, expiresAt: "2026-10-17T00:00:00.000Z" },
210
+ { cwd: dir },
211
+ );
212
+ const commit = git(dir, "rev-parse", "chant/lifecycle");
213
+ const { gates } = await readMemberGates("_gates", "flat", commit, ["prod"], dir, now);
214
+ expect(gates).toEqual([expect.objectContaining({ component: "release", releaseDigest, changeSet: d.digest, state: "pending" })]);
215
+ });
216
+ });
217
+ });