@intentius/chant 0.124.31 → 0.124.33

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 (509) hide show
  1. package/bin/chant +7 -2
  2. package/dist/ci-jobs.d.ts +4 -0
  3. package/dist/ci-jobs.d.ts.map +1 -1
  4. package/dist/ci-jobs.js +9 -2
  5. package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
  6. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  7. package/dist/cli/commands/check-lexicon-plugin.js +10 -7
  8. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  9. package/dist/cli/commands/check-lexicon.js +18 -12
  10. package/dist/cli/commands/import.d.ts +10 -0
  11. package/dist/cli/commands/import.d.ts.map +1 -1
  12. package/dist/cli/commands/import.js +37 -0
  13. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
  15. package/dist/cli/commands/onboard.d.ts.map +1 -1
  16. package/dist/cli/commands/onboard.js +8 -4
  17. package/dist/cli/commands/update.d.ts.map +1 -1
  18. package/dist/cli/commands/update.js +2 -1
  19. package/dist/cli/dependencies.d.ts +18 -0
  20. package/dist/cli/dependencies.d.ts.map +1 -0
  21. package/dist/cli/dependencies.js +120 -0
  22. package/dist/cli/handlers/build.d.ts.map +1 -1
  23. package/dist/cli/handlers/build.js +38 -4
  24. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  25. package/dist/cli/handlers/fan-out.js +12 -4
  26. package/dist/cli/handlers/graph.d.ts.map +1 -1
  27. package/dist/cli/handlers/graph.js +16 -6
  28. package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
  29. package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
  30. package/dist/cli/handlers/lifecycle-health.js +150 -0
  31. package/dist/cli/handlers/lifecycle.d.ts +26 -0
  32. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  33. package/dist/cli/handlers/lifecycle.js +1 -1
  34. package/dist/cli/handlers/lint.d.ts.map +1 -1
  35. package/dist/cli/handlers/lint.js +6 -0
  36. package/dist/cli/handlers/operator.d.ts +2 -2
  37. package/dist/cli/handlers/operator.js +3 -3
  38. package/dist/cli/handlers/pr.d.ts +16 -0
  39. package/dist/cli/handlers/pr.d.ts.map +1 -1
  40. package/dist/cli/handlers/pr.js +610 -32
  41. package/dist/cli/handlers/promote.d.ts.map +1 -1
  42. package/dist/cli/handlers/promote.js +19 -2
  43. package/dist/cli/handlers/release.d.ts +16 -1
  44. package/dist/cli/handlers/release.d.ts.map +1 -1
  45. package/dist/cli/handlers/release.js +83 -11
  46. package/dist/cli/handlers/run.d.ts +13 -1
  47. package/dist/cli/handlers/run.d.ts.map +1 -1
  48. package/dist/cli/handlers/run.js +38 -15
  49. package/dist/cli/main.d.ts.map +1 -1
  50. package/dist/cli/main.js +121 -33
  51. package/dist/cli/parse-args.d.ts.map +1 -1
  52. package/dist/cli/parse-args.js +37 -0
  53. package/dist/cli/registry.d.ts +29 -6
  54. package/dist/cli/registry.d.ts.map +1 -1
  55. package/dist/cli/registry.js +6 -2
  56. package/dist/codegen/generate-post-synth-barrel.js +2 -2
  57. package/dist/codegen/lexicon-regen.d.ts +4 -0
  58. package/dist/codegen/lexicon-regen.d.ts.map +1 -1
  59. package/dist/codegen/lexicon-regen.js +1 -1
  60. package/dist/codegen/pinned-upgrade.js +2 -2
  61. package/dist/components/capability.d.ts +44 -3
  62. package/dist/components/capability.d.ts.map +1 -1
  63. package/dist/components/capability.js +21 -0
  64. package/dist/components/changed-files.d.ts +7 -1
  65. package/dist/components/changed-files.d.ts.map +1 -1
  66. package/dist/components/changed-files.js +9 -1
  67. package/dist/components/deploy-inputs.d.ts +91 -0
  68. package/dist/components/deploy-inputs.d.ts.map +1 -0
  69. package/dist/components/deploy-inputs.js +184 -0
  70. package/dist/components/driver.d.ts +38 -0
  71. package/dist/components/driver.d.ts.map +1 -1
  72. package/dist/components/driver.js +56 -6
  73. package/dist/components/fan-out-record.d.ts +6 -1
  74. package/dist/components/fan-out-record.d.ts.map +1 -1
  75. package/dist/components/fan-out-record.js +24 -0
  76. package/dist/components/fan-out-run.d.ts +20 -0
  77. package/dist/components/fan-out-run.d.ts.map +1 -1
  78. package/dist/components/fan-out-run.js +21 -1
  79. package/dist/components/gate-plan.d.ts +8 -0
  80. package/dist/components/gate-plan.d.ts.map +1 -1
  81. package/dist/components/gate-plan.js +6 -0
  82. package/dist/components/pr-pipeline.d.ts +98 -12
  83. package/dist/components/pr-pipeline.d.ts.map +1 -1
  84. package/dist/components/pr-pipeline.js +146 -23
  85. package/dist/components/pr-run.d.ts +128 -30
  86. package/dist/components/pr-run.d.ts.map +1 -1
  87. package/dist/components/pr-run.js +290 -70
  88. package/dist/components/release-compile.d.ts +37 -2
  89. package/dist/components/release-compile.d.ts.map +1 -1
  90. package/dist/components/release-compile.js +64 -8
  91. package/dist/components/release-promote.d.ts +17 -3
  92. package/dist/components/release-promote.d.ts.map +1 -1
  93. package/dist/components/release-promote.js +79 -18
  94. package/dist/components/verbs/bom-writer.d.ts +1 -1
  95. package/dist/components/verbs/bom-writer.d.ts.map +1 -1
  96. package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
  97. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
  98. package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
  99. package/dist/components/wave-plan.d.ts +15 -2
  100. package/dist/components/wave-plan.d.ts.map +1 -1
  101. package/dist/components/wave-plan.js +44 -5
  102. package/dist/discovery/fold-import.d.ts.map +1 -1
  103. package/dist/discovery/fold-import.js +16 -6
  104. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  105. package/dist/discovery/sandbox/bundle.js +6 -0
  106. package/dist/discovery/specifier-resolve.d.ts.map +1 -1
  107. package/dist/discovery/specifier-resolve.js +8 -2
  108. package/dist/fold/fold.d.ts.map +1 -1
  109. package/dist/fold/fold.js +68 -1
  110. package/dist/fold/subset.d.ts +37 -0
  111. package/dist/fold/subset.d.ts.map +1 -1
  112. package/dist/fold/subset.js +88 -0
  113. package/dist/graph-detail.d.ts.map +1 -1
  114. package/dist/graph-detail.js +32 -7
  115. package/dist/health.d.ts +110 -0
  116. package/dist/health.d.ts.map +1 -0
  117. package/dist/health.js +105 -0
  118. package/dist/health.schema.json +191 -0
  119. package/dist/lexicon-module.d.ts +10 -0
  120. package/dist/lexicon-module.d.ts.map +1 -1
  121. package/dist/lexicon-module.js +53 -8
  122. package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
  123. package/dist/lexicon-plugin-helpers.js +6 -4
  124. package/dist/lexicon.d.ts +29 -0
  125. package/dist/lexicon.d.ts.map +1 -1
  126. package/dist/lifecycle/affected.d.ts.map +1 -1
  127. package/dist/lifecycle/affected.js +7 -1
  128. package/dist/lifecycle/merge-record.d.ts +104 -0
  129. package/dist/lifecycle/merge-record.d.ts.map +1 -0
  130. package/dist/lifecycle/merge-record.js +102 -0
  131. package/dist/lifecycle/publish-attachments.d.ts +8 -5
  132. package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
  133. package/dist/lifecycle/publish-attachments.js +27 -13
  134. package/dist/lifecycle/publish-npm.d.ts +3 -2
  135. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  136. package/dist/lifecycle/publish-npm.js +4 -3
  137. package/dist/lifecycle/release-at.d.ts +40 -0
  138. package/dist/lifecycle/release-at.d.ts.map +1 -0
  139. package/dist/lifecycle/release-at.js +83 -0
  140. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  141. package/dist/lifecycle/release-cut.js +23 -12
  142. package/dist/lifecycle/release-ledger.d.ts +21 -0
  143. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  144. package/dist/lifecycle/release-ledger.js +24 -0
  145. package/dist/lifecycle/release-publish.d.ts +28 -12
  146. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  147. package/dist/lifecycle/release-publish.js +29 -17
  148. package/dist/lifecycle/release-store.d.ts +25 -2
  149. package/dist/lifecycle/release-store.d.ts.map +1 -1
  150. package/dist/lifecycle/release-store.js +40 -13
  151. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  152. package/dist/lifecycle/run-ledger.js +1 -0
  153. package/dist/lifecycle/work-dir.d.ts +45 -0
  154. package/dist/lifecycle/work-dir.d.ts.map +1 -0
  155. package/dist/lifecycle/work-dir.js +78 -0
  156. package/dist/lint/discover.d.ts +17 -9
  157. package/dist/lint/discover.d.ts.map +1 -1
  158. package/dist/lint/discover.js +62 -54
  159. package/dist/lint/rules/op/index.d.ts +2 -1
  160. package/dist/lint/rules/op/index.d.ts.map +1 -1
  161. package/dist/lint/rules/op/index.js +4 -2
  162. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
  163. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
  164. package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
  165. package/dist/module-source.d.ts +57 -0
  166. package/dist/module-source.d.ts.map +1 -0
  167. package/dist/module-source.js +104 -0
  168. package/dist/op/activities/activity-contracts.d.ts +5 -0
  169. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  170. package/dist/op/activities/activity-contracts.js +3 -0
  171. package/dist/op/activities/factory.js +1 -1
  172. package/dist/op/activities/shell.d.ts +38 -0
  173. package/dist/op/activities/shell.d.ts.map +1 -1
  174. package/dist/op/activities/shell.js +89 -3
  175. package/dist/op/builders.d.ts +7 -0
  176. package/dist/op/builders.d.ts.map +1 -1
  177. package/dist/op/builders.js +7 -0
  178. package/dist/op/discover.d.ts.map +1 -1
  179. package/dist/op/discover.js +13 -3
  180. package/dist/op/gate.d.ts.map +1 -1
  181. package/dist/op/gate.js +3 -0
  182. package/dist/op/op-waves-run.d.ts.map +1 -1
  183. package/dist/op/op-waves-run.js +20 -5
  184. package/dist/op/operator.d.ts +2 -2
  185. package/dist/op/operator.js +2 -2
  186. package/dist/op/runtime.d.ts +2 -0
  187. package/dist/op/runtime.d.ts.map +1 -1
  188. package/dist/op/steward.d.ts +6 -4
  189. package/dist/op/steward.d.ts.map +1 -1
  190. package/dist/op/steward.js +5 -3
  191. package/dist/op/work-lease-run.d.ts +20 -7
  192. package/dist/op/work-lease-run.d.ts.map +1 -1
  193. package/dist/op/work-lease-run.js +56 -12
  194. package/dist/pr-forge.d.ts +10 -0
  195. package/dist/pr-forge.d.ts.map +1 -1
  196. package/dist/pr-forge.js +39 -0
  197. package/dist/pr-loop.d.ts +155 -7
  198. package/dist/pr-loop.d.ts.map +1 -1
  199. package/dist/pr-loop.js +137 -11
  200. package/dist/pr-report-url.d.ts +4 -4
  201. package/dist/pr-report-url.d.ts.map +1 -1
  202. package/dist/pr-report-url.js +5 -5
  203. package/dist/runner-start.d.ts +179 -0
  204. package/dist/runner-start.d.ts.map +1 -0
  205. package/dist/runner-start.js +277 -0
  206. package/dist/ts-loader.d.ts +38 -4
  207. package/dist/ts-loader.d.ts.map +1 -1
  208. package/dist/ts-loader.js +190 -16
  209. package/dist/wave-runtime.mjs +238 -231
  210. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
  211. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
  212. package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
  213. package/dist/workspace/chant-migrations.d.ts.map +1 -1
  214. package/dist/workspace/chant-migrations.js +2 -0
  215. package/dist/workspace/check.schema.json +70 -2
  216. package/dist/workspace/checks/boxes.d.ts +6 -6
  217. package/dist/workspace/checks/boxes.js +9 -9
  218. package/dist/workspace/checks/security.d.ts +35 -0
  219. package/dist/workspace/checks/security.d.ts.map +1 -0
  220. package/dist/workspace/checks/security.js +305 -0
  221. package/dist/workspace/checks.d.ts +2 -0
  222. package/dist/workspace/checks.d.ts.map +1 -1
  223. package/dist/workspace/checks.js +5 -0
  224. package/dist/workspace/ci-builds.d.ts +163 -0
  225. package/dist/workspace/ci-builds.d.ts.map +1 -0
  226. package/dist/workspace/ci-builds.js +315 -0
  227. package/dist/workspace/ci-green-cli.d.ts +2 -1
  228. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  229. package/dist/workspace/ci-green-cli.js +25 -5
  230. package/dist/workspace/ci-green-forge.d.ts +18 -4
  231. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  232. package/dist/workspace/ci-green-forge.js +12 -0
  233. package/dist/workspace/ci-green-workflow.d.ts +55 -1
  234. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  235. package/dist/workspace/ci-green-workflow.js +128 -1
  236. package/dist/workspace/ci-green.d.ts +117 -16
  237. package/dist/workspace/ci-green.d.ts.map +1 -1
  238. package/dist/workspace/ci-green.js +358 -43
  239. package/dist/workspace/ci-last-green.schema.json +49 -0
  240. package/dist/workspace/ci-list.schema.json +47 -7
  241. package/dist/workspace/ci-report-url.d.ts +18 -0
  242. package/dist/workspace/ci-report-url.d.ts.map +1 -0
  243. package/dist/workspace/ci-report-url.js +33 -0
  244. package/dist/workspace/ci-run-index.d.ts +13 -1
  245. package/dist/workspace/ci-run-index.d.ts.map +1 -1
  246. package/dist/workspace/ci-run-index.js +6 -4
  247. package/dist/workspace/ci-run-index.schema.json +6 -4
  248. package/dist/workspace/ci-run.d.ts +85 -6
  249. package/dist/workspace/ci-run.d.ts.map +1 -1
  250. package/dist/workspace/ci-run.js +262 -64
  251. package/dist/workspace/ci-runner-key.d.ts +16 -3
  252. package/dist/workspace/ci-runner-key.d.ts.map +1 -1
  253. package/dist/workspace/ci-runner-key.js +30 -10
  254. package/dist/workspace/ci-runner-start.d.ts +77 -0
  255. package/dist/workspace/ci-runner-start.d.ts.map +1 -0
  256. package/dist/workspace/ci-runner-start.js +185 -0
  257. package/dist/workspace/ci-serve.d.ts +130 -11
  258. package/dist/workspace/ci-serve.d.ts.map +1 -1
  259. package/dist/workspace/ci-serve.js +323 -40
  260. package/dist/workspace/declaration.d.ts +25 -0
  261. package/dist/workspace/declaration.d.ts.map +1 -1
  262. package/dist/workspace/declaration.js +14 -2
  263. package/dist/workspace/declaration.schema.json +30 -2
  264. package/dist/workspace/export.d.ts +1 -1
  265. package/dist/workspace/export.d.ts.map +1 -1
  266. package/dist/workspace/export.js +7 -3
  267. package/dist/workspace/ls.d.ts +8 -1
  268. package/dist/workspace/ls.d.ts.map +1 -1
  269. package/dist/workspace/ls.js +2 -1
  270. package/dist/workspace/ls.schema.json +9 -2
  271. package/dist/workspace/member-pipeline.d.ts +1 -0
  272. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  273. package/dist/workspace/member-pipeline.js +2 -0
  274. package/dist/workspace/merge-record.schema.json +49 -0
  275. package/dist/workspace/pin-integrity.d.ts.map +1 -1
  276. package/dist/workspace/pin-integrity.js +3 -2
  277. package/dist/workspace/pr-report.schema.json +75 -8
  278. package/dist/workspace/reason-codes.d.ts +18 -1
  279. package/dist/workspace/reason-codes.d.ts.map +1 -1
  280. package/dist/workspace/reason-codes.js +20 -2
  281. package/dist/workspace/release-candidates.d.ts +129 -0
  282. package/dist/workspace/release-candidates.d.ts.map +1 -0
  283. package/dist/workspace/release-candidates.js +233 -0
  284. package/dist/workspace/release-candidates.schema.json +353 -0
  285. package/dist/workspace/run-event.schema.json +3 -1
  286. package/dist/workspace/security.d.ts +40 -0
  287. package/dist/workspace/security.d.ts.map +1 -0
  288. package/dist/workspace/security.js +99 -0
  289. package/dist/workspace/status.schema.json +3 -1
  290. package/dist/workspace/write-scope.d.ts +9 -0
  291. package/dist/workspace/write-scope.d.ts.map +1 -1
  292. package/dist/workspace/write-scope.js +11 -1
  293. package/package.json +8 -1
  294. package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
  295. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
  296. package/src/ci-jobs.ts +11 -2
  297. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
  298. package/src/cli/commands/audit.test.ts +2 -2
  299. package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
  300. package/src/cli/commands/check-lexicon-plugin.ts +11 -8
  301. package/src/cli/commands/check-lexicon.test.ts +1 -1
  302. package/src/cli/commands/check-lexicon.ts +16 -11
  303. package/src/cli/commands/import.ts +40 -0
  304. package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
  305. package/src/cli/commands/init-lexicon.test.ts +8 -5
  306. package/src/cli/commands/onboard.test.ts +45 -0
  307. package/src/cli/commands/onboard.ts +8 -4
  308. package/src/cli/commands/update.test.ts +6 -0
  309. package/src/cli/commands/update.ts +4 -1
  310. package/src/cli/compiled-cli.e2e.test.ts +76 -11
  311. package/src/cli/dependencies.e2e.test.ts +101 -0
  312. package/src/cli/dependencies.test.ts +93 -0
  313. package/src/cli/dependencies.ts +122 -0
  314. package/src/cli/handlers/build.test.ts +105 -0
  315. package/src/cli/handlers/build.ts +35 -4
  316. package/src/cli/handlers/fan-out.test.ts +73 -0
  317. package/src/cli/handlers/fan-out.ts +11 -4
  318. package/src/cli/handlers/graph.test.ts +76 -0
  319. package/src/cli/handlers/graph.ts +19 -12
  320. package/src/cli/handlers/lifecycle-health.test.ts +123 -0
  321. package/src/cli/handlers/lifecycle-health.ts +157 -0
  322. package/src/cli/handlers/lifecycle.ts +1 -1
  323. package/src/cli/handlers/lint.test.ts +42 -0
  324. package/src/cli/handlers/lint.ts +6 -0
  325. package/src/cli/handlers/operator.ts +3 -3
  326. package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
  327. package/src/cli/handlers/pr-record.test.ts +201 -0
  328. package/src/cli/handlers/pr-waves.test.ts +449 -0
  329. package/src/cli/handlers/pr.test.ts +156 -0
  330. package/src/cli/handlers/pr.ts +648 -33
  331. package/src/cli/handlers/promote.test.ts +43 -1
  332. package/src/cli/handlers/promote.ts +19 -3
  333. package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
  334. package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
  335. package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
  336. package/src/cli/handlers/release.e2e.test.ts +19 -28
  337. package/src/cli/handlers/release.ts +81 -11
  338. package/src/cli/handlers/run-components-exit.test.ts +99 -0
  339. package/src/cli/handlers/run-signer.test.ts +79 -0
  340. package/src/cli/handlers/run.ts +34 -16
  341. package/src/cli/main.test.ts +18 -0
  342. package/src/cli/main.ts +120 -33
  343. package/src/cli/parse-args.ts +28 -0
  344. package/src/cli/registry.ts +35 -8
  345. package/src/codegen/generate-post-synth-barrel.ts +2 -2
  346. package/src/codegen/lexicon-regen.ts +1 -1
  347. package/src/codegen/pinned-upgrade.ts +2 -2
  348. package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
  349. package/src/components/capability.ts +51 -3
  350. package/src/components/changed-files.test.ts +16 -1
  351. package/src/components/changed-files.ts +9 -1
  352. package/src/components/deploy-inputs.test.ts +108 -0
  353. package/src/components/deploy-inputs.ts +225 -0
  354. package/src/components/driver.test.ts +36 -1
  355. package/src/components/driver.ts +87 -4
  356. package/src/components/fan-out-record.ts +29 -1
  357. package/src/components/fan-out-run.test.ts +35 -0
  358. package/src/components/fan-out-run.ts +40 -1
  359. package/src/components/gate-plan.ts +9 -0
  360. package/src/components/pr-pipeline.test.ts +100 -0
  361. package/src/components/pr-pipeline.ts +184 -28
  362. package/src/components/pr-run.test.ts +323 -18
  363. package/src/components/pr-run.ts +355 -76
  364. package/src/components/release-compile.test.ts +34 -0
  365. package/src/components/release-compile.ts +89 -8
  366. package/src/components/release-promote.test.ts +55 -2
  367. package/src/components/release-promote.ts +93 -23
  368. package/src/components/verbs/bom-writer.ts +1 -1
  369. package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
  370. package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
  371. package/src/components/wave-plan.ts +48 -8
  372. package/src/discovery/fold-host-method.test.ts +87 -0
  373. package/src/discovery/fold-import.ts +15 -5
  374. package/src/discovery/fold-package-composite.test.ts +27 -0
  375. package/src/discovery/fold-specifiers.test.ts +8 -0
  376. package/src/discovery/sandbox/bundle.ts +6 -0
  377. package/src/discovery/specifier-resolve.ts +8 -2
  378. package/src/fold/fold.test.ts +72 -0
  379. package/src/fold/fold.ts +71 -0
  380. package/src/fold/subset.test.ts +31 -0
  381. package/src/fold/subset.ts +99 -0
  382. package/src/graph-detail.test.ts +57 -0
  383. package/src/graph-detail.ts +33 -7
  384. package/src/health.schema.json +191 -0
  385. package/src/health.test.ts +62 -0
  386. package/src/health.ts +178 -0
  387. package/src/lexicon-dist.e2e.test.ts +254 -0
  388. package/src/lexicon-module.test.ts +47 -0
  389. package/src/lexicon-module.ts +49 -7
  390. package/src/lexicon-plugin-helpers.ts +6 -4
  391. package/src/lexicon.ts +43 -1
  392. package/src/lifecycle/affected.test.ts +7 -0
  393. package/src/lifecycle/affected.ts +6 -1
  394. package/src/lifecycle/merge-record.test.ts +111 -0
  395. package/src/lifecycle/merge-record.ts +154 -0
  396. package/src/lifecycle/publish-attachments.ts +25 -13
  397. package/src/lifecycle/publish-npm.ts +4 -3
  398. package/src/lifecycle/publish-targets.test.ts +34 -4
  399. package/src/lifecycle/release-at.test.ts +48 -0
  400. package/src/lifecycle/release-at.ts +100 -0
  401. package/src/lifecycle/release-cut.test.ts +41 -0
  402. package/src/lifecycle/release-cut.ts +22 -11
  403. package/src/lifecycle/release-ledger.ts +34 -0
  404. package/src/lifecycle/release-publish.test.ts +43 -13
  405. package/src/lifecycle/release-publish.ts +38 -18
  406. package/src/lifecycle/release-store.ts +57 -13
  407. package/src/lifecycle/run-ledger.test.ts +13 -0
  408. package/src/lifecycle/run-ledger.ts +1 -0
  409. package/src/lifecycle/work-dir.test.ts +77 -0
  410. package/src/lifecycle/work-dir.ts +82 -0
  411. package/src/lint/discover.test.ts +46 -2
  412. package/src/lint/discover.ts +55 -49
  413. package/src/lint/rules/op/index.ts +4 -2
  414. package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
  415. package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
  416. package/src/module-source.test.ts +85 -0
  417. package/src/module-source.ts +112 -0
  418. package/src/op/activities/activity-contracts.ts +3 -0
  419. package/src/op/activities/factory.ts +1 -1
  420. package/src/op/activities/shell-container.e2e.test.ts +67 -0
  421. package/src/op/activities/shell.test.ts +44 -1
  422. package/src/op/activities/shell.ts +110 -3
  423. package/src/op/builders.ts +7 -0
  424. package/src/op/discover-root.test.ts +31 -2
  425. package/src/op/discover.ts +14 -4
  426. package/src/op/gate.test.ts +30 -0
  427. package/src/op/gate.ts +3 -0
  428. package/src/op/op-waves-run.ts +20 -5
  429. package/src/op/op-waves.test.ts +49 -0
  430. package/src/op/operator.ts +2 -2
  431. package/src/op/runtime.ts +2 -0
  432. package/src/op/step-console.test.ts +4 -2
  433. package/src/op/steward.ts +6 -4
  434. package/src/op/work-lease-run.test.ts +88 -2
  435. package/src/op/work-lease-run.ts +52 -12
  436. package/src/pr-forge-bundle.test.ts +2 -2
  437. package/src/pr-forge.test.ts +42 -0
  438. package/src/pr-forge.ts +40 -0
  439. package/src/pr-loop-bundle.test.ts +2 -1
  440. package/src/pr-loop.test.ts +146 -1
  441. package/src/pr-loop.ts +278 -17
  442. package/src/pr-report-url.ts +7 -7
  443. package/src/runner-start-bundle.test.ts +29 -0
  444. package/src/runner-start.test.ts +179 -0
  445. package/src/runner-start.ts +329 -0
  446. package/src/terraform/__fixtures__/advise.test.ts +21 -2
  447. package/src/ts-loader.test.ts +89 -1
  448. package/src/ts-loader.ts +180 -13
  449. package/src/workspace/changes.test.ts +1 -1
  450. package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
  451. package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
  452. package/src/workspace/chant-migrations.e2e.test.ts +5 -0
  453. package/src/workspace/chant-migrations.ts +2 -0
  454. package/src/workspace/check-contract.test.ts +3 -2
  455. package/src/workspace/check.schema.json +70 -2
  456. package/src/workspace/checks/boxes.ts +9 -9
  457. package/src/workspace/checks/security.test.ts +138 -0
  458. package/src/workspace/checks/security.ts +305 -0
  459. package/src/workspace/checks.ts +5 -0
  460. package/src/workspace/ci-builds.e2e.test.ts +208 -0
  461. package/src/workspace/ci-builds.test.ts +174 -0
  462. package/src/workspace/ci-builds.ts +385 -0
  463. package/src/workspace/ci-green-builds.test.ts +180 -0
  464. package/src/workspace/ci-green-cli.test.ts +54 -0
  465. package/src/workspace/ci-green-cli.ts +26 -5
  466. package/src/workspace/ci-green-forge.ts +30 -4
  467. package/src/workspace/ci-green-runs.test.ts +53 -11
  468. package/src/workspace/ci-green-workflow.test.ts +84 -1
  469. package/src/workspace/ci-green-workflow.ts +139 -2
  470. package/src/workspace/ci-green.test.ts +6 -3
  471. package/src/workspace/ci-green.ts +408 -48
  472. package/src/workspace/ci-last-green.schema.json +49 -0
  473. package/src/workspace/ci-list.schema.json +47 -7
  474. package/src/workspace/ci-report-url.test.ts +220 -0
  475. package/src/workspace/ci-report-url.ts +33 -0
  476. package/src/workspace/ci-run-index.schema.json +6 -4
  477. package/src/workspace/ci-run-index.test.ts +42 -3
  478. package/src/workspace/ci-run-index.ts +19 -5
  479. package/src/workspace/ci-run.test.ts +203 -8
  480. package/src/workspace/ci-run.ts +295 -63
  481. package/src/workspace/ci-runner-key.test.ts +55 -17
  482. package/src/workspace/ci-runner-key.ts +36 -11
  483. package/src/workspace/ci-runner-start.test.ts +151 -0
  484. package/src/workspace/ci-runner-start.ts +193 -0
  485. package/src/workspace/ci-serve-builds.test.ts +86 -0
  486. package/src/workspace/ci-serve.test.ts +209 -6
  487. package/src/workspace/ci-serve.ts +341 -42
  488. package/src/workspace/declaration.schema.json +30 -2
  489. package/src/workspace/declaration.test.ts +40 -0
  490. package/src/workspace/declaration.ts +40 -3
  491. package/src/workspace/export.ts +6 -3
  492. package/src/workspace/ls.schema.json +9 -2
  493. package/src/workspace/ls.ts +10 -4
  494. package/src/workspace/member-pipeline.ts +2 -1
  495. package/src/workspace/merge-record.schema.json +49 -0
  496. package/src/workspace/pin-integrity.test.ts +5 -0
  497. package/src/workspace/pin-integrity.ts +3 -2
  498. package/src/workspace/pr-report.schema.json +75 -8
  499. package/src/workspace/reason-codes.test.ts +2 -0
  500. package/src/workspace/reason-codes.ts +20 -2
  501. package/src/workspace/release-candidates.schema.json +353 -0
  502. package/src/workspace/release-candidates.test.ts +106 -0
  503. package/src/workspace/release-candidates.ts +315 -0
  504. package/src/workspace/run-event.schema.json +3 -1
  505. package/src/workspace/security.test.ts +70 -0
  506. package/src/workspace/security.ts +118 -0
  507. package/src/workspace/status.schema.json +3 -1
  508. package/src/workspace/write-scope.test.ts +29 -1
  509. package/src/workspace/write-scope.ts +12 -1
@@ -8,26 +8,42 @@
8
8
  * everything before applying anything is what makes the two digests
9
9
  * comparable: each member is planned against the same state both times.
10
10
  *
11
- * ## Dependents read the outputs standing now
11
+ * ## What a dependent is planned against
12
12
  *
13
13
  * A member that reads another one's output through `stackOutput()` is
14
- * planned with the value that output has now, read through the capability's
15
- * `outputs` (`readComponentOutputs`). When the change moves that output, the
16
- * dependent's approved plan still carries the old value; the apply applies
17
- * it as approved and marks the member `inputsMoved`, and the next run plans
18
- * it against the new value. A change that must carry a moved output through
19
- * its dependents in one rollout is what gated waves are for (#3049): each
20
- * wave is planned after the one before it applied.
14
+ * planned with one of three values (#3999):
21
15
  *
22
- * A dependency the change did not select is read the same way (#4104), so
23
- * a change to one cluster plans it against the network it sits on. That is
24
- * why `components` is every component in the project and not the selection.
25
- * `--seed-outputs` wins over a read, and a dependency whose steps cannot say
26
- * leaves its dependents' references unresolved. The apply stage's plan reads
27
- * the same dependencies, so on the same estate it gets the digest the plan
28
- * stage showed, and it applies against the outputs its plans read
29
- * (`set.outputs`). A resume plans against the outputs the approved plans
30
- * read, from the record (`planOutputs`), and reads nothing those cover.
16
+ * - the upstream is in the change too: the value its plan says it will
17
+ * expose (`CapabilityPlan.outputs`). The report lists these under
18
+ * `plannedOutputs`.
19
+ * - the upstream is not in the change (#4104): the value it has now, read
20
+ * through its capability's `outputs` (`readComponentOutputs`). The report
21
+ * lists these under `deployedOutputs`. A change to one cluster is planned
22
+ * against the network it sits on, which is why `components` is every
23
+ * component in the project and not the selection.
24
+ * - the upstream is in the change and its plan cannot say, or says the value
25
+ * is known only after apply: no value. The dependent's members list the
26
+ * reference under `unknownInputs`, and the approval does not cover what
27
+ * it will do with it.
28
+ *
29
+ * `--seed-outputs` wins over both. The apply stage's plan reads the same
30
+ * values, so on the same estate it gets the digest the plan stage showed.
31
+ * A resume plans against the outputs the approved plans read, from the
32
+ * record (`planOutputs`), and reads nothing those cover.
33
+ *
34
+ * ## Planning again once an upstream applied
35
+ *
36
+ * The apply runs each member's approved plan, except a member whose plan
37
+ * read an upstream output that is not the value standing when its turn
38
+ * comes (`movedWiringReferences`): the upstream applied as another value
39
+ * than its plan said, or the value was unknown. That member, and what is
40
+ * downstream of it, is held back. Once the rest settled, the held members
41
+ * are planned again against the outputs the apply left, and that set's
42
+ * digest is decided at its own gate, `<gate>-round-<n>` (`prRoundGate`), as
43
+ * a gated wave decides each wave (#3049). Approved, it applies, and a member
44
+ * that is held again goes to the next round. Not approved, the apply stops
45
+ * `waiting` with the new digest named, and the same apply with its
46
+ * `--resume` record carries on from there.
31
47
  *
32
48
  * ## Finishing an apply that failed partway
33
49
  *
@@ -41,29 +57,48 @@
41
57
  * the rest applies. Planning everything again would give the applied members
42
58
  * empty plans and the set a new digest, and so ask for a second approval of
43
59
  * work already approved.
60
+ *
61
+ * ## A gate per wave on merge
62
+ *
63
+ * `pr-apply --wave-gate` (#4003) applies the approved set a wave at a time.
64
+ * {@link prWavePlan} narrows the derivation to one wave, which is planned
65
+ * against the outputs the waves before it left, and {@link waveNotCovered}
66
+ * says whether the approved change set covers what it planned, by the same
67
+ * test a resume uses ({@link planCoveredBy}). Covered, the wave applies under
68
+ * the pull request's approval; not covered, it needs its own.
44
69
  */
45
70
 
46
71
  import { composeChangeSet, verifyChangeSetDigest, type ChangeSetDocument, type ChangeSetEntry, type ChangeSetPart } from "../change-set";
47
72
  import { evaluateGate, type GateCheck, type GateLedgerPort } from "../op/gate";
48
73
  import {
49
74
  memberCounts,
75
+ PR_APPLY_GATE,
76
+ prRoundGate,
50
77
  PR_REPORT_CONTRACT,
51
78
  PR_REPORT_SCHEMA_ID,
52
79
  type PrApproval,
53
80
  type PrMember,
54
81
  type PrRefusal,
82
+ type PrRound,
55
83
  type PrReport,
56
84
  type PrStatus,
85
+ type PrWave,
57
86
  } from "../pr-loop";
58
- import { deployContext, type DriverComponent, type DriverComponentResult } from "./driver";
87
+ import {
88
+ deployContext,
89
+ movedWiringReferences,
90
+ unresolvedWiringReferences,
91
+ type DriverComponent,
92
+ type DriverComponentResult,
93
+ } from "./driver";
59
94
  import { deployUnits } from "./deploy-units";
60
95
  import { runFanOut, type FanOutRunResult } from "./fan-out-run";
61
96
  import { remainingFanOut, type ComponentChangeSignal, type FanOutPlan } from "./fan-out";
62
97
  import type { PrApplyRecord } from "./fan-out-record";
63
- import type { CapabilityRegistry } from "./capability";
98
+ import type { CapabilityRegistry, PlannedOutputs } from "./capability";
64
99
  import type { RunProgressEvent } from "./run-progress";
65
100
  import type { RunEventSink } from "../run-event-stream";
66
- import { planWaveComponent, readDeployedOutputs } from "./wave-plan";
101
+ import { capabilitySteps, planWaveComponent, readDeployedOutputs } from "./wave-plan";
67
102
 
68
103
  export interface PrPlanSetOptions {
69
104
  /** Every component in the project, not the selection: a planned member's dependency outside it is read too (#4104). */
@@ -101,6 +136,12 @@ export interface PrPlanSet {
101
136
  * nothing was read. The report's `deployedOutputs`.
102
137
  */
103
138
  deployedOutputs?: Record<string, string[]>;
139
+ /**
140
+ * Per dependency in the change that a member read (#3999), the names of
141
+ * the outputs its plan knows, sorted. Absent when there is none. The
142
+ * report's `plannedOutputs`.
143
+ */
144
+ plannedOutputs?: Record<string, string[]>;
104
145
  /** Whether any component failed to plan. */
105
146
  failed: boolean;
106
147
  }
@@ -115,36 +156,51 @@ function failedParts(component: DriverComponent, error: string): ChangeSetPart[]
115
156
  }
116
157
 
117
158
  /**
118
- * Plan every component in `plan.order`, a wave at a time, each against the
119
- * outputs its dependencies have now. Never throws for a component that fails
120
- * to plan: it becomes a failed member, and `failed` is set. Throws when a
121
- * planned member's dependency is not among `components`, which means the
122
- * caller passed the selection where the whole graph belongs.
159
+ * Plan every component in `plan.order`, a wave at a time, each against its
160
+ * dependencies' planned outputs when the change plans them too, and against
161
+ * the outputs they have now otherwise. Never throws for a component that
162
+ * fails to plan: it becomes a failed member, and `failed` is set. Throws
163
+ * when a planned member's dependency is not among `components`, which means
164
+ * the caller passed the selection where the whole graph belongs.
123
165
  */
124
166
  export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
125
167
  const byName = new Map(options.components.map((c) => [c.name, c]));
126
168
  const outputs: Record<string, Record<string, unknown>> = { ...(options.seededOutputs ?? {}) };
127
169
  const read = new Set(Object.keys(outputs));
170
+ const inChange = new Set(options.plan.order);
171
+ /** What each component of the change planned to expose, once its wave planned. */
172
+ const plannedBy = new Map<string, PlannedOutputs | undefined>();
128
173
  const deployed: Record<string, string[]> = {};
174
+ const planned: Record<string, string[]> = {};
129
175
  const parts: ChangeSetPart[] = [];
130
176
  const componentOf = new Map<string, string>();
177
+ const unknownOf = new Map<string, { unknown: string[]; build: string[] }>();
131
178
  const plans: Record<string, Record<string, unknown>> = {};
132
179
  let failed = false;
133
180
  const emit = options.onRunEvent;
134
181
  emit?.({ type: "phase-start", component: null, phase: PR_PLAN_PHASE });
135
182
 
136
183
  for (const wave of options.plan.waves) {
137
- // The dependencies' outputs first, each read once. A dependency that
138
- // cannot say leaves its references unresolved, which its dependent's
139
- // plan reports if it needs them. A dependency outside the selection is
140
- // read the same way (#4104).
184
+ // The dependencies' outputs first, each once. One the change plans is
185
+ // read from its plan, which an earlier wave made (#3999). Any other is
186
+ // read from the deployed estate (#4104). A dependency that cannot say
187
+ // leaves its references unresolved, which its dependent's plan reports
188
+ // if it needs them.
141
189
  const deps = [...new Set(wave.flatMap((name) => byName.get(name)?.dependsOn ?? []))].filter((dep) => !read.has(dep));
190
+ const fromEstate: string[] = [];
142
191
  for (const dep of deps) {
143
192
  read.add(dep);
144
193
  if (!byName.has(dep)) throw new Error(`planPrSet: ${dep}, a dependency of a planned member, is not among the components; pass every component in the project`);
194
+ if (!inChange.has(dep)) {
195
+ fromEstate.push(dep);
196
+ continue;
197
+ }
198
+ const known = plannedBy.get(dep)?.known ?? {};
199
+ outputs[dep] = { ...known };
200
+ planned[dep] = Object.keys(known).sort();
145
201
  }
146
- Object.assign(deployed, await readDeployedOutputs(deps, byName, options.registry, (dep) => deployContext(options, dep), outputs));
147
- const planned = await Promise.all(
202
+ Object.assign(deployed, await readDeployedOutputs(fromEstate, byName, options.registry, (dep) => deployContext(options, dep), outputs));
203
+ const results = await Promise.all(
148
204
  wave.map(async (name) => {
149
205
  const at = { component: name, phase: PR_PLAN_PHASE, step: "plan" };
150
206
  emit?.({ type: "step-start", ...at });
@@ -160,16 +216,20 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
160
216
  return p;
161
217
  }),
162
218
  );
163
- for (const p of planned) {
219
+ for (const p of results) {
164
220
  const component = byName.get(p.component)!;
221
+ const unknown = unknownInputsOf(component, outputs, inChange);
222
+ if (unknown.unknown.length > 0 || unknown.build.length > 0) unknownOf.set(p.component, unknown);
165
223
  if ("error" in p) {
166
224
  failed = true;
225
+ plannedBy.set(p.component, undefined);
167
226
  for (const part of failedParts(component, p.error)) {
168
227
  parts.push(part);
169
228
  componentOf.set(part.member.member, p.component);
170
229
  }
171
230
  continue;
172
231
  }
232
+ plannedBy.set(p.component, p.outputs);
173
233
  plans[p.component] = p.plans;
174
234
  for (const part of p.parts) {
175
235
  parts.push(part);
@@ -180,16 +240,78 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
180
240
 
181
241
  emit?.({ type: "phase-settle", component: null, phase: PR_PLAN_PHASE, status: failed ? "failed" : "ok" });
182
242
  const doc = composeChangeSet(parts);
183
- const members: PrMember[] = doc.members.map((m) => ({
184
- member: m.member,
185
- component: componentOf.get(m.member) ?? m.member,
186
- planDigest: m.planDigest,
187
- status: m.status === "failed" ? "plan-failed" : "planned",
188
- counts: memberCounts(doc, m.member),
189
- ...(m.error ? { error: m.error } : {}),
190
- }));
191
- const deployedOutputs = Object.fromEntries(Object.entries(deployed).sort(([a], [b]) => a.localeCompare(b)));
192
- return { doc, members, plans, outputs, failed, ...(Object.keys(deployedOutputs).length > 0 ? { deployedOutputs } : {}) };
243
+ const members: PrMember[] = doc.members.map((m) => {
244
+ const component = componentOf.get(m.member) ?? m.member;
245
+ const unknown = unknownOf.get(component);
246
+ return {
247
+ member: m.member,
248
+ component,
249
+ planDigest: m.planDigest,
250
+ status: m.status === "failed" ? "plan-failed" : "planned",
251
+ counts: memberCounts(doc, m.member),
252
+ ...(m.error ? { error: m.error } : {}),
253
+ ...(unknown?.unknown.length ? { unknownInputs: unknown.unknown } : {}),
254
+ ...(unknown?.build.length ? { buildInputs: unknown.build } : {}),
255
+ };
256
+ });
257
+ const sorted = (r: Record<string, string[]>) => Object.fromEntries(Object.entries(r).sort(([a], [b]) => a.localeCompare(b)));
258
+ const deployedOutputs = sorted(deployed);
259
+ const plannedOutputs = sorted(planned);
260
+ return {
261
+ doc,
262
+ members,
263
+ plans,
264
+ outputs,
265
+ failed,
266
+ ...(Object.keys(deployedOutputs).length > 0 ? { deployedOutputs } : {}),
267
+ ...(Object.keys(plannedOutputs).length > 0 ? { plannedOutputs } : {}),
268
+ };
269
+ }
270
+
271
+ /**
272
+ * The references `component`'s steps make to an output of a component in
273
+ * the change that `outputs` holds no value for (#3999), known only after
274
+ * that upstream applies, each sorted. `build` are the published artifact
275
+ * identities a build in the change produces (`WiringReference.publish`):
276
+ * the build's provenance binds those, so they are never approved again.
277
+ * `unknown` are the rest.
278
+ */
279
+ function unknownInputsOf(
280
+ component: DriverComponent,
281
+ outputs: Record<string, Record<string, unknown>>,
282
+ inChange: ReadonlySet<string>,
283
+ ): { unknown: string[]; build: string[] } {
284
+ const unknown = new Set<string>();
285
+ const build = new Set<string>();
286
+ for (const step of capabilitySteps(component.deploy)) {
287
+ const { kind: _kind, ...rest } = step;
288
+ for (const r of unresolvedWiringReferences(rest, outputs)) {
289
+ if (inChange.has(r.source)) (r.publish ? build : unknown).add(r.ref);
290
+ }
291
+ }
292
+ return { unknown: [...unknown].sort(), build: [...build].sort() };
293
+ }
294
+
295
+ /**
296
+ * Why `component` cannot apply the plan that read `read` now that `now`
297
+ * stands (#3999): each reference its steps make that resolves to another
298
+ * value, said once. `undefined` when every one resolves as it did. A
299
+ * published artifact's identity from a build in `inChange` never holds a
300
+ * member back: the build's provenance binds it, not this approval.
301
+ */
302
+ export function heldReason(
303
+ component: DriverComponent,
304
+ read: Record<string, Record<string, unknown>>,
305
+ now: Record<string, Record<string, unknown>>,
306
+ inChange: ReadonlySet<string> = new Set(),
307
+ ): string | undefined {
308
+ const refs = new Set<string>();
309
+ for (const step of capabilitySteps(component.deploy)) {
310
+ const { kind: _kind, ...rest } = step;
311
+ for (const r of movedWiringReferences(rest, read, now)) if (!(r.publish && inChange.has(r.source))) refs.add(r.ref);
312
+ }
313
+ if (refs.size === 0) return undefined;
314
+ return `its plan read ${[...refs].sort().join(", ")} before ${refs.size === 1 ? "that output" : "those outputs"} applied as another value`;
193
315
  }
194
316
 
195
317
  /**
@@ -242,6 +364,18 @@ export function approversWithoutReview(approvedBy: readonly string[], reviewers:
242
364
  return approvedBy.filter((by) => !allowed.has(norm(by)));
243
365
  }
244
366
 
367
+ /**
368
+ * Who approved the gate but wrote the change (#4000): each approval's
369
+ * `resolvedBy` that names one of `authors`, the forge's naming of the pull
370
+ * request's author and of its commits' authors and committers. Their own
371
+ * approval never counts, whether a forge review or a ledger line backs it.
372
+ */
373
+ export function approversWhoAuthored(approvedBy: readonly string[], authors: readonly string[]): string[] {
374
+ const norm = (s: string) => s.trim().toLowerCase();
375
+ const wrote = new Set(authors.map(norm));
376
+ return approvedBy.filter((by) => wrote.has(norm(by)));
377
+ }
378
+
245
379
  export interface PrApplyOptions {
246
380
  components: DriverComponent[];
247
381
  plan: FanOutPlan;
@@ -261,58 +395,151 @@ export interface PrApplyOptions {
261
395
  resumed?: { completed: string[]; outputs: Record<string, Record<string, unknown>> };
262
396
  /** Called as each component settles, so an attempt record survives a kill (`runFanOut`). */
263
397
  onComponentSettled?: (result: DriverComponentResult, outputs: Record<string, unknown> | undefined) => void;
398
+ /** The pull request's gate, which names each later round's gate ({@link prRoundGate}). Default `pr-apply`. */
399
+ gate?: string;
400
+ /**
401
+ * Decide the gate over the plans of a round after the first (#3999): the
402
+ * members held back because an upstream they read applied as another value
403
+ * than their plan read, planned again against the outputs the apply left.
404
+ * The gate is {@link prRoundGate}. Without it, held members are left
405
+ * `waiting` and nothing is planned again.
406
+ */
407
+ decideRound?: (round: { round: number; gate: string; set: PrPlanSet; plan: FanOutPlan }) => Promise<PrRoundDecision>;
264
408
  }
265
409
 
266
- const sameJson = (a: unknown, b: unknown): boolean => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
410
+ /** How a round's gate was decided: whether it applies, and where its approval stands. */
411
+ export interface PrRoundDecision {
412
+ satisfied: boolean;
413
+ approval: PrApproval;
414
+ /** Why it does not apply, when an approval stands but does not count (`--require-review`). */
415
+ message?: string;
416
+ }
417
+
418
+ export interface PrApplyResult {
419
+ members: PrMember[];
420
+ /** `waiting`: some members wait on an approval of the plan a later round made. */
421
+ status: "applied" | "failed" | "waiting";
422
+ /** Every round's runs together: their results, components and outputs. */
423
+ run: FanOutRunResult;
424
+ /** Each round after the first, in order. Absent when nothing was held back. */
425
+ rounds?: PrRound[];
426
+ }
427
+
428
+ /** Two runs of one apply as one: `b` ran after `a`, against the outputs `a` left. */
429
+ function joinRuns(a: FanOutRunResult, b: FanOutRunResult): FanOutRunResult {
430
+ const status: FanOutRunResult["status"] = a.status === "fail" || b.status === "fail" ? "fail" : a.status === "gated" || b.status === "gated" ? "gated" : "ok";
431
+ const gate = a.gate ?? b.gate;
432
+ return {
433
+ plan: a.plan,
434
+ status,
435
+ results: [...a.results, ...b.results],
436
+ completed: [...new Set([...a.completed, ...b.completed])].sort(),
437
+ failed: [...new Set([...a.failed, ...b.failed])].sort(),
438
+ blocked: [...a.blocked, ...b.blocked],
439
+ componentOutputs: b.componentOutputs,
440
+ ...(gate ? { gate } : {}),
441
+ ...(b.held ? { held: b.held } : {}),
442
+ };
443
+ }
267
444
 
268
445
  /**
269
446
  * Apply the approved plans in dependency order. A failed component blocks
270
- * its dependents and nothing else (`runFanOut`).
447
+ * its dependents and nothing else (`runFanOut`). A member whose plan read an
448
+ * upstream output that is not the value standing when its turn comes is held
449
+ * back with what is downstream of it, planned again once the rest settled,
450
+ * and applied when `decideRound` approves that plan (#3999).
271
451
  */
272
- export async function applyPrSet(options: PrApplyOptions): Promise<{ members: PrMember[]; status: "applied" | "failed"; run: FanOutRunResult }> {
273
- const before = options.set.outputs;
274
- const run = await runFanOut(options.plan, options.components, options.registry, {
275
- env: options.env,
276
- ...(options.vars ? { vars: options.vars } : {}),
277
- plans: options.set.plans,
278
- componentOutputs: { ...before },
279
- ...(options.onProgress ? { onProgress: options.onProgress } : {}),
280
- ...(options.onRunEvent ? { onRunEvent: options.onRunEvent } : {}),
281
- ...(options.onComponentSettled ? { onComponentSettled: options.onComponentSettled } : {}),
282
- });
283
- const completed = new Set(run.completed);
452
+ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult> {
453
+ const byName = new Map(options.components.map((c) => [c.name, c]));
284
454
  const priorCompleted = new Set(options.resumed?.completed ?? []);
285
- /**
286
- * Whether a dependency's outputs moved from what the plans read: by this
287
- * run's apply, or by the earlier attempt's when the record kept them.
288
- */
289
- const movedBy = (dep: string): boolean => {
290
- if (completed.has(dep)) return !sameJson(before[dep], run.componentOutputs[dep]);
291
- const earlier = options.resumed?.outputs;
292
- return priorCompleted.has(dep) && earlier !== undefined && dep in earlier && !sameJson(before[dep], earlier[dep]);
455
+ const inChange = new Set([...options.set.members.map((m) => m.component), ...options.plan.order]);
456
+ const toApply = new Set([...inChange].filter((name) => !priorCompleted.has(name)));
457
+ // The outputs standing as the apply starts: what the plans read of
458
+ // components outside the change, and what an earlier attempt's components
459
+ // left (the values the plans read, where its record kept none). A planned
460
+ // value of a component still to apply stands nowhere until it applies.
461
+ const standing: Record<string, Record<string, unknown>> = {
462
+ ...Object.fromEntries(Object.entries(options.set.outputs).filter(([name]) => !toApply.has(name))),
463
+ ...(options.resumed?.outputs ?? {}),
293
464
  };
465
+ const runRound = (plan: FanOutPlan, plans: Record<string, Record<string, unknown>>, read: Record<string, Record<string, unknown>>, outputs: Record<string, Record<string, unknown>>) =>
466
+ runFanOut(plan, options.components, options.registry, {
467
+ env: options.env,
468
+ ...(options.vars ? { vars: options.vars } : {}),
469
+ plans,
470
+ componentOutputs: outputs,
471
+ hold: (name, now) => heldReason(byName.get(name)!, read, now, inChange),
472
+ ...(options.onProgress ? { onProgress: options.onProgress } : {}),
473
+ ...(options.onRunEvent ? { onRunEvent: options.onRunEvent } : {}),
474
+ ...(options.onComponentSettled ? { onComponentSettled: options.onComponentSettled } : {}),
475
+ });
476
+
477
+ let run = await runRound(options.plan, options.set.plans, options.set.outputs, { ...standing });
478
+ const rounds: PrRound[] = [];
479
+ /** Per component, the round that applied it or holds it, and that round's set, after the first. */
480
+ const roundOf = new Map<string, { round: number; set: PrPlanSet }>();
481
+ let stopped: "waiting" | "plan-failed" | undefined;
482
+ while (run.held && run.held.length > 0 && run.status === "ok" && options.decideRound) {
483
+ const round = rounds.length + 2;
484
+ const gate = prRoundGate(options.gate ?? PR_APPLY_GATE, round);
485
+ const heldNames = new Set(run.held.map((h) => h.component));
486
+ const plan = remainingFanOut(options.plan, options.components, { completed: options.plan.order.filter((name) => !heldNames.has(name)) });
487
+ const set = await planPrSet({
488
+ components: options.components,
489
+ plan,
490
+ registry: options.registry,
491
+ env: options.env,
492
+ ...(options.vars ? { vars: options.vars } : {}),
493
+ seededOutputs: run.componentOutputs,
494
+ });
495
+ for (const name of plan.order) roundOf.set(name, { round, set });
496
+ const record = (status: PrRound["status"], approval: PrApproval, message?: string): void => {
497
+ rounds.push({ round, gate, digest: set.doc.digest, components: plan.order, approval, status, ...(message ? { message } : {}), changeSet: set.doc });
498
+ };
499
+ if (set.failed) {
500
+ record("plan-failed", { status: "pending" });
501
+ stopped = "plan-failed";
502
+ break;
503
+ }
504
+ const decision = await options.decideRound({ round, gate, set, plan });
505
+ if (!decision.satisfied) {
506
+ record("waiting", decision.approval, decision.message);
507
+ stopped = "waiting";
508
+ break;
509
+ }
510
+ const next = await runRound(plan, set.plans, set.outputs, run.componentOutputs);
511
+ record(next.status === "fail" ? "failed" : "applied", decision.approval);
512
+ run = joinRuns(run, next);
513
+ }
514
+
515
+ const completed = new Set(run.completed);
516
+ const held = new Map((run.held ?? []).map((h) => [h.component, h]));
294
517
  const blocked = new Set(run.blocked.map((b) => b.component));
295
518
  const errorOf = (component: string): string | undefined => {
296
- const result = run.results.find((r) => r.component === component);
519
+ const result = [...run.results].reverse().find((r) => r.component === component);
297
520
  if (result?.status === "gated") return `stopped at its own gate ${result.gate?.gate ?? ""}`.trim();
298
521
  return result?.records.find((r) => r.status === "fail")?.error;
299
522
  };
300
- const byName = new Map(options.components.map((c) => [c.name, c]));
301
523
  const members = options.set.members.map((m): PrMember => {
302
524
  if (priorCompleted.has(m.component)) return { ...m, status: "applied" };
303
- if (completed.has(m.component)) {
304
- const moved = (byName.get(m.component)?.dependsOn ?? [])
305
- .filter(movedBy)
306
- .sort();
307
- return { ...m, status: "applied", ...(moved.length > 0 ? { inputsMoved: moved } : {}) };
308
- }
309
- if (blocked.has(m.component)) return { ...m, status: "blocked" };
525
+ const later = roundOf.get(m.component);
526
+ const fresh = later?.set.members.find((x) => x.member === m.member);
527
+ const asPlanned: PrMember = later && fresh ? { ...m, planDigest: fresh.planDigest, counts: fresh.counts, round: later.round } : m;
528
+ if (completed.has(m.component)) return { ...asPlanned, status: "applied" };
529
+ const h = held.get(m.component);
530
+ if (h) return { ...asPlanned, status: "waiting", held: h.heldBy ? `${h.heldBy} is held: ${h.reason}` : h.reason };
531
+ if (fresh?.status === "plan-failed") return { ...asPlanned, status: "plan-failed", ...(fresh.error ? { error: fresh.error } : {}) };
532
+ if (blocked.has(m.component)) return { ...asPlanned, status: "blocked" };
310
533
  const error = errorOf(m.component);
311
- return { ...m, status: "failed", ...(error ? { error } : {}) };
534
+ return { ...asPlanned, status: "failed", ...(error ? { error } : {}) };
312
535
  });
313
- return { members, status: run.status === "ok" ? "applied" : "failed", run };
536
+ const status: PrApplyResult["status"] =
537
+ run.status !== "ok" || stopped === "plan-failed" ? "failed" : held.size > 0 || stopped === "waiting" ? "waiting" : "applied";
538
+ return { members, status, run, ...(rounds.length > 0 ? { rounds } : {}) };
314
539
  }
315
540
 
541
+ const sameJson = (a: unknown, b: unknown): boolean => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
542
+
316
543
  const entryKey = (e: Pick<ChangeSetEntry, "member" | "address" | "deposed">): string => `${e.member}\u0000${e.address}\u0000${e.deposed ?? ""}`;
317
544
 
318
545
  /**
@@ -466,8 +693,51 @@ export async function resumePrSet(options: PrResumeOptions): Promise<PrResume> {
466
693
  };
467
694
  }
468
695
 
696
+ /**
697
+ * The fan-out narrowed to wave `wave` of `plan` (1-based), less the
698
+ * components in `completed` (#4003). Its one wave is what is left of that
699
+ * wave; a dependency outside it, an earlier wave's component among them, is
700
+ * a seed. The digest is the derivation's, as {@link remainingFanOut} keeps it.
701
+ */
702
+ export function prWavePlan(plan: FanOutPlan, components: DriverComponent[], wave: number, completed: readonly string[] = []): FanOutPlan {
703
+ const done = new Set(completed);
704
+ const order = (plan.waves[wave - 1] ?? []).filter((name) => !done.has(name));
705
+ const inWave = new Set(order);
706
+ const byName = new Map(components.map((c) => [c.name, c]));
707
+ const seeds = [
708
+ ...new Set([...plan.seeds, ...order.flatMap((name) => (byName.get(name)?.dependsOn ?? []).filter((dep) => !inWave.has(dep)))]),
709
+ ].sort();
710
+ return { order, waves: order.length > 0 ? [order] : [], skipped: [], seeds, indeterminate: plan.indeterminate, digest: plan.digest };
711
+ }
712
+
713
+ /**
714
+ * Why the approved change set does not cover a wave's new plan, one line per
715
+ * member, or `[]` when it covers every member (#4003). A member is covered
716
+ * as {@link planCoveredBy} says, with one rule more: a member whose planner
717
+ * describes no changes (chant's own empty part, for a capability that
718
+ * returns no change set) is covered only by the digest it was approved
719
+ * with, since with no entries there is nothing to compare a moved plan by.
720
+ */
721
+ export function waveNotCovered(approved: ChangeSetDocument, fresh: ChangeSetDocument, approvedMembers: readonly PrMember[] = []): string[] {
722
+ // #3999: a member whose approved plan waited only on an artifact a build in
723
+ // the change publishes is covered by that build's provenance, as it is
724
+ // without --wave-gate.
725
+ const byBuild = new Set(approvedMembers.filter((m) => m.buildInputs?.length && !m.unknownInputs?.length).map((m) => m.member));
726
+ return fresh.members
727
+ .map((m) => {
728
+ if (byBuild.has(m.member)) return undefined;
729
+ const was = approved.members.find((a) => a.member === m.member);
730
+ const opaque = m.planner === "chant" && m.lexicon === "chant" && m.status === "planned";
731
+ if (opaque && was && was.planDigest !== m.planDigest) {
732
+ return `${m.member} now plans to ${m.planDigest}, not the approved ${was.planDigest ?? "(none)"}, and its planner describes no changes to compare`;
733
+ }
734
+ return planCoveredBy(approved, fresh, m.member);
735
+ })
736
+ .filter((r): r is string => r !== undefined);
737
+ }
738
+
469
739
  export interface PrReportInput {
470
- stage: "plan" | "apply";
740
+ stage: "plan" | "apply" | "record";
471
741
  pr: number | null;
472
742
  env: string;
473
743
  /** The workspace member whose pipeline runs the stage (#3465). */
@@ -486,6 +756,11 @@ export interface PrReportInput {
486
756
  message?: string;
487
757
  /** Components an earlier attempt at the approved set applied (#3464). */
488
758
  resumed?: string[];
759
+ /** `--wave-gate` (#4003): the wave gate's base name, and each wave reached so far. */
760
+ waveGate?: string;
761
+ waves?: PrWave[];
762
+ /** The rounds the apply planned after an upstream applied (#3999). */
763
+ rounds?: PrRound[];
489
764
  }
490
765
 
491
766
  /** The report a stage writes. */
@@ -508,7 +783,11 @@ export function prReport(input: PrReportInput): PrReport {
508
783
  ...(input.message ? { message: input.message } : {}),
509
784
  ...(input.resumed ? { resumed: { applied: [...input.resumed].sort() } } : {}),
510
785
  ...(input.set.deployedOutputs ? { deployedOutputs: input.set.deployedOutputs } : {}),
786
+ ...(input.set.plannedOutputs ? { plannedOutputs: input.set.plannedOutputs } : {}),
787
+ ...(input.rounds && input.rounds.length > 0 ? { rounds: input.rounds } : {}),
511
788
  approval: input.approval,
789
+ ...(input.waveGate ? { waveGate: input.waveGate } : {}),
790
+ ...(input.waveGate && input.waves?.length ? { waves: [...input.waves].sort((a, b) => a.wave - b.wave) } : {}),
512
791
  selection: {
513
792
  changed: input.plan.order.filter((c) => changed.has(c)),
514
793
  dependents: input.plan.order.filter((c) => !changed.has(c)),
@@ -13,6 +13,7 @@ import {
13
13
  buildReleaseArtifacts,
14
14
  buildStepsOf,
15
15
  compileEnvironment,
16
+ recordedInputRefs,
16
17
  ReleaseCompileError,
17
18
  templateRefs,
18
19
  withOnlyArchiveSteps,
@@ -49,6 +50,18 @@ describe("templateRefs", () => {
49
50
  });
50
51
  });
51
52
 
53
+ describe("recordedInputRefs (#4155)", () => {
54
+ test("adds each kind's own fields to the template fields: a chart; a missing one need not exist", () => {
55
+ expect(recordedInputRefs(api).map((r) => [r.kind, r.field, r.path, r.required])).toEqual([
56
+ ["cfn-deploy", "template", "dist/api.template.json", true],
57
+ ["cfn-deploy", "template", "dist/edge.template.json", true],
58
+ ["helm-upgrade", "values", "values/base.yaml", true],
59
+ ["helm-upgrade", "chart", "charts/api", false],
60
+ ["kubectl-apply", "manifest", "k8s/api.yaml", true],
61
+ ]);
62
+ });
63
+ });
64
+
52
65
  describe("withOnlyArchiveSteps", () => {
53
66
  test("keeps the archive steps alone, drops gates and every other step, and the dependencies", () => {
54
67
  expect(withOnlyArchiveSteps(api)).toEqual({
@@ -100,6 +113,27 @@ describe("compileEnvironment", () => {
100
113
  });
101
114
  });
102
115
 
116
+ test("records a chart directory file by file and names it in trees; a file that is not UTF-8 as base64 (#4155)", async () => {
117
+ await withTestDir(async (dir) => {
118
+ writeFileSync(
119
+ join(dir, "web.component.ts"),
120
+ `export const web = { name: "web", dependsOn: [], deploy: [{ phase: "Deploy", steps: [
121
+ { kind: "helm-upgrade", release: "web", chart: "chart" },
122
+ { kind: "helm-upgrade", release: "remote", chart: "bitnami/nginx", repo: "https://charts.example" },
123
+ ] }] };`,
124
+ );
125
+ mkdirSync(join(dir, "chart", "charts"), { recursive: true });
126
+ writeFileSync(join(dir, "chart", "Chart.yaml"), "name: web\n");
127
+ writeFileSync(join(dir, "chart", "charts", "dep.tgz"), Buffer.from([0x1f, 0x8b, 0xff, 0x00]));
128
+ const target = await compileEnvironment({ projectDir: dir }, "prod");
129
+ expect(target.trees).toEqual([{ component: "web", path: "chart" }]);
130
+ expect(target.templates).toEqual([
131
+ { component: "web", path: "chart/Chart.yaml", content: "name: web\n" },
132
+ { component: "web", path: "chart/charts/dep.tgz", content: Buffer.from([0x1f, 0x8b, 0xff, 0x00]).toString("base64"), encoding: "base64" },
133
+ ]);
134
+ });
135
+ });
136
+
103
137
  test("refuses when a template a deploy names does not exist after the build", async () => {
104
138
  await withTestDir(async (dir) => {
105
139
  project(dir);