@intentius/chant 0.124.46 → 0.124.49

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 (726) hide show
  1. package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  2. package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  3. package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  4. package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  6. package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  7. package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
  8. package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
  9. package/dist/__golden__/wave-loop/op-denied.json +16 -0
  10. package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  11. package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  12. package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
  13. package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
  14. package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
  15. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  16. package/dist/__golden__/wave-loop/op-held-on.json +16 -0
  17. package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  18. package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  19. package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  20. package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  21. package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
  22. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  23. package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
  24. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  25. package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
  26. package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
  27. package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  28. package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  29. package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
  30. package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  31. package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  32. package/dist/__golden__/wave-loop/op-overridden.json +16 -0
  33. package/dist/__golden__/wave-loop/op-per-run.json +20 -0
  34. package/dist/__golden__/wave-loop/op-point.json +13 -0
  35. package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
  36. package/dist/__golden__/wave-loop/op-sealed.json +10 -0
  37. package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
  38. package/dist/__golden__/wave-loop/op-spend.json +10 -0
  39. package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
  40. package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
  41. package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
  42. package/dist/__golden__/wave-loop/op-wide.json +39 -0
  43. package/dist/action-pins.d.ts +61 -0
  44. package/dist/action-pins.d.ts.map +1 -0
  45. package/dist/action-pins.js +77 -0
  46. package/dist/cli/commands/carve.d.ts +3 -0
  47. package/dist/cli/commands/carve.d.ts.map +1 -1
  48. package/dist/cli/commands/carve.js +36 -7
  49. package/dist/cli/commands/import-report.d.ts +26 -0
  50. package/dist/cli/commands/import-report.d.ts.map +1 -0
  51. package/dist/cli/commands/import-report.js +65 -0
  52. package/dist/cli/commands/import.d.ts +11 -1
  53. package/dist/cli/commands/import.d.ts.map +1 -1
  54. package/dist/cli/commands/import.js +22 -3
  55. package/dist/cli/commands/migrate.d.ts +13 -1
  56. package/dist/cli/commands/migrate.d.ts.map +1 -1
  57. package/dist/cli/commands/migrate.js +134 -10
  58. package/dist/cli/handlers/approve.d.ts.map +1 -1
  59. package/dist/cli/handlers/approve.js +41 -14
  60. package/dist/cli/handlers/build.d.ts.map +1 -1
  61. package/dist/cli/handlers/build.js +6 -1
  62. package/dist/cli/handlers/graph-reader.d.ts +26 -0
  63. package/dist/cli/handlers/graph-reader.d.ts.map +1 -0
  64. package/dist/cli/handlers/graph-reader.js +41 -0
  65. package/dist/cli/handlers/graph.d.ts.map +1 -1
  66. package/dist/cli/handlers/graph.js +14 -0
  67. package/dist/cli/handlers/ledger.d.ts +3 -0
  68. package/dist/cli/handlers/ledger.d.ts.map +1 -0
  69. package/dist/cli/handlers/ledger.js +55 -0
  70. package/dist/cli/handlers/migrate.d.ts.map +1 -1
  71. package/dist/cli/handlers/migrate.js +18 -11
  72. package/dist/cli/handlers/misc.d.ts.map +1 -1
  73. package/dist/cli/handlers/misc.js +12 -1
  74. package/dist/cli/handlers/pr-env.js +2 -2
  75. package/dist/cli/handlers/pr.d.ts +15 -1
  76. package/dist/cli/handlers/pr.d.ts.map +1 -1
  77. package/dist/cli/handlers/pr.js +183 -40
  78. package/dist/cli/handlers/query.d.ts +7 -2
  79. package/dist/cli/handlers/query.d.ts.map +1 -1
  80. package/dist/cli/handlers/query.js +16 -4
  81. package/dist/cli/handlers/release.d.ts.map +1 -1
  82. package/dist/cli/handlers/release.js +6 -3
  83. package/dist/cli/handlers/search.d.ts +2 -4
  84. package/dist/cli/handlers/search.d.ts.map +1 -1
  85. package/dist/cli/handlers/search.js +13 -39
  86. package/dist/cli/main.d.ts.map +1 -1
  87. package/dist/cli/main.js +87 -14
  88. package/dist/cli/parse-args.d.ts.map +1 -1
  89. package/dist/cli/parse-args.js +47 -2
  90. package/dist/cli/registry.d.ts +22 -4
  91. package/dist/cli/registry.d.ts.map +1 -1
  92. package/dist/components/builders.d.ts +5 -1
  93. package/dist/components/builders.d.ts.map +1 -1
  94. package/dist/components/builders.js +5 -0
  95. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  96. package/dist/components/capability-plugin-loader.js +16 -1
  97. package/dist/components/capability-plugin.d.ts +14 -0
  98. package/dist/components/capability-plugin.d.ts.map +1 -1
  99. package/dist/components/pr-hooks.d.ts +21 -1
  100. package/dist/components/pr-hooks.d.ts.map +1 -1
  101. package/dist/components/pr-hooks.js +72 -4
  102. package/dist/components/pr-pipeline.d.ts +45 -10
  103. package/dist/components/pr-pipeline.d.ts.map +1 -1
  104. package/dist/components/pr-pipeline.js +47 -14
  105. package/dist/components/pr-policy.d.ts +36 -0
  106. package/dist/components/pr-policy.d.ts.map +1 -1
  107. package/dist/components/pr-policy.js +36 -0
  108. package/dist/components/pr-run.d.ts +3 -0
  109. package/dist/components/pr-run.d.ts.map +1 -1
  110. package/dist/components/pr-run.js +1 -0
  111. package/dist/components/starter-plugin.d.ts +1 -0
  112. package/dist/components/starter-plugin.d.ts.map +1 -1
  113. package/dist/components/starter-plugin.js +5 -1
  114. package/dist/components/verbs/index.d.ts +2 -0
  115. package/dist/components/verbs/index.d.ts.map +1 -1
  116. package/dist/components/verbs/index.js +2 -0
  117. package/dist/components/verbs/publish-artifact.d.ts +65 -0
  118. package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
  119. package/dist/components/verbs/publish-artifact.js +74 -0
  120. package/dist/components/verbs/publish-image.d.ts +129 -0
  121. package/dist/components/verbs/publish-image.d.ts.map +1 -0
  122. package/dist/components/verbs/publish-image.js +145 -0
  123. package/dist/config-environments.d.ts +20 -0
  124. package/dist/config-environments.d.ts.map +1 -1
  125. package/dist/config-environments.js +12 -0
  126. package/dist/config.d.ts +25 -1
  127. package/dist/config.d.ts.map +1 -1
  128. package/dist/config.js +10 -2
  129. package/dist/gate-decision.d.ts +20 -2
  130. package/dist/gate-decision.d.ts.map +1 -1
  131. package/dist/gate-decision.js +30 -3
  132. package/dist/graph-ir.d.ts +25 -0
  133. package/dist/graph-ir.d.ts.map +1 -1
  134. package/dist/graph-walk.d.ts +66 -0
  135. package/dist/graph-walk.d.ts.map +1 -0
  136. package/dist/graph-walk.js +70 -0
  137. package/dist/import/parser.d.ts +19 -0
  138. package/dist/import/parser.d.ts.map +1 -1
  139. package/dist/lexicon.d.ts +94 -6
  140. package/dist/lexicon.d.ts.map +1 -1
  141. package/dist/lifecycle/gate-ledger.d.ts +11 -0
  142. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  143. package/dist/lifecycle/git.d.ts +2 -5
  144. package/dist/lifecycle/git.d.ts.map +1 -1
  145. package/dist/lifecycle/ledger-read.d.ts +65 -0
  146. package/dist/lifecycle/ledger-read.d.ts.map +1 -0
  147. package/dist/lifecycle/ledger-read.js +160 -0
  148. package/dist/lifecycle/release-workstation.d.ts +7 -0
  149. package/dist/lifecycle/release-workstation.d.ts.map +1 -1
  150. package/dist/lifecycle/release-workstation.js +25 -0
  151. package/dist/lifecycle/run-ledger.d.ts +4 -3
  152. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  153. package/dist/lifecycle/run-ledger.js +1 -0
  154. package/dist/lint/discover.js +3 -1
  155. package/dist/lint/post-synth.d.ts +127 -1
  156. package/dist/lint/post-synth.d.ts.map +1 -1
  157. package/dist/lint/post-synth.js +84 -2
  158. package/dist/lint/rules/op/index.d.ts +1 -0
  159. package/dist/lint/rules/op/index.d.ts.map +1 -1
  160. package/dist/lint/rules/op/index.js +2 -0
  161. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
  162. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
  163. package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
  164. package/dist/op/activities/activity-contracts.d.ts +9 -6
  165. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  166. package/dist/op/activities/activity-contracts.js +5 -2
  167. package/dist/op/activities/index.d.ts +2 -2
  168. package/dist/op/activities/index.d.ts.map +1 -1
  169. package/dist/op/activities/index.js +1 -1
  170. package/dist/op/activities/review-agent.d.ts +90 -4
  171. package/dist/op/activities/review-agent.d.ts.map +1 -1
  172. package/dist/op/activities/review-agent.js +217 -19
  173. package/dist/op/activities/work-evidence.d.ts +8 -1
  174. package/dist/op/activities/work-evidence.d.ts.map +1 -1
  175. package/dist/op/activities/work-evidence.js +1 -0
  176. package/dist/op/builders.d.ts +9 -5
  177. package/dist/op/builders.d.ts.map +1 -1
  178. package/dist/op/builders.js +9 -5
  179. package/dist/op/composites/apply-op.d.ts +29 -2
  180. package/dist/op/composites/apply-op.d.ts.map +1 -1
  181. package/dist/op/composites/apply-op.js +20 -1
  182. package/dist/op/gate-approval-base.d.ts +25 -0
  183. package/dist/op/gate-approval-base.d.ts.map +1 -0
  184. package/dist/op/gate-approval-base.js +71 -0
  185. package/dist/op/gate-approval.d.ts +18 -9
  186. package/dist/op/gate-approval.d.ts.map +1 -1
  187. package/dist/op/gate-approval.js +13 -31
  188. package/dist/op/gate-name.d.ts +13 -0
  189. package/dist/op/gate-name.d.ts.map +1 -1
  190. package/dist/op/gate-name.js +14 -1
  191. package/dist/op/gate-quorum.d.ts +100 -3
  192. package/dist/op/gate-quorum.d.ts.map +1 -1
  193. package/dist/op/gate-quorum.js +108 -6
  194. package/dist/op/gate-review.d.ts +9 -0
  195. package/dist/op/gate-review.d.ts.map +1 -1
  196. package/dist/op/gate-review.js +13 -0
  197. package/dist/op/gate.d.ts +47 -5
  198. package/dist/op/gate.d.ts.map +1 -1
  199. package/dist/op/gate.js +25 -4
  200. package/dist/op/index.d.ts +3 -3
  201. package/dist/op/index.d.ts.map +1 -1
  202. package/dist/op/index.js +1 -1
  203. package/dist/op/local-executor.d.ts +45 -3
  204. package/dist/op/local-executor.d.ts.map +1 -1
  205. package/dist/op/local-executor.js +68 -7
  206. package/dist/op/op-waves-run.d.ts +26 -1
  207. package/dist/op/op-waves-run.d.ts.map +1 -1
  208. package/dist/op/op-waves-run.js +259 -13
  209. package/dist/op/op-waves.d.ts +72 -5
  210. package/dist/op/op-waves.d.ts.map +1 -1
  211. package/dist/op/op-waves.js +110 -15
  212. package/dist/op/runtime.d.ts +11 -0
  213. package/dist/op/runtime.d.ts.map +1 -1
  214. package/dist/op/schedule-overdue.d.ts +16 -0
  215. package/dist/op/schedule-overdue.d.ts.map +1 -1
  216. package/dist/op/schedule-overdue.js +36 -0
  217. package/dist/op/stage-hooks.d.ts +9 -2
  218. package/dist/op/stage-hooks.d.ts.map +1 -1
  219. package/dist/op/stage-hooks.js +7 -5
  220. package/dist/op/workspace-run-env.d.ts +22 -0
  221. package/dist/op/workspace-run-env.d.ts.map +1 -0
  222. package/dist/op/workspace-run-env.js +21 -0
  223. package/dist/pr-forge.d.ts +24 -0
  224. package/dist/pr-forge.d.ts.map +1 -1
  225. package/dist/pr-forge.js +47 -0
  226. package/dist/pr-loop.d.ts +10 -0
  227. package/dist/pr-loop.d.ts.map +1 -1
  228. package/dist/pr-loop.js +4 -0
  229. package/dist/pr-migrations.d.ts +62 -0
  230. package/dist/pr-migrations.d.ts.map +1 -0
  231. package/dist/pr-migrations.js +50 -0
  232. package/dist/pr-post.d.ts +42 -0
  233. package/dist/pr-post.d.ts.map +1 -1
  234. package/dist/pr-post.js +68 -2
  235. package/dist/read-filter.d.ts +65 -0
  236. package/dist/read-filter.d.ts.map +1 -0
  237. package/dist/read-filter.js +179 -0
  238. package/dist/run-event.d.ts +1 -1
  239. package/dist/run-event.d.ts.map +1 -1
  240. package/dist/terraform/carve.d.ts +24 -3
  241. package/dist/terraform/carve.d.ts.map +1 -1
  242. package/dist/terraform/carve.js +32 -14
  243. package/dist/terraform/cuts.d.ts +97 -0
  244. package/dist/terraform/cuts.d.ts.map +1 -0
  245. package/dist/terraform/cuts.js +138 -0
  246. package/dist/terraform/folds.d.ts +26 -0
  247. package/dist/terraform/folds.d.ts.map +1 -0
  248. package/dist/terraform/folds.js +38 -0
  249. package/dist/terraform/graph.d.ts.map +1 -1
  250. package/dist/terraform/graph.js +10 -0
  251. package/dist/terraform/reach.d.ts +140 -0
  252. package/dist/terraform/reach.d.ts.map +1 -0
  253. package/dist/terraform/reach.js +170 -0
  254. package/dist/terraform/score.d.ts +14 -0
  255. package/dist/terraform/score.d.ts.map +1 -1
  256. package/dist/terraform/score.js +14 -31
  257. package/dist/terraform/state.d.ts.map +1 -1
  258. package/dist/terraform/state.js +4 -2
  259. package/dist/terraform/types.d.ts +8 -0
  260. package/dist/terraform/types.d.ts.map +1 -1
  261. package/dist/wave-blocked.d.ts +10 -1
  262. package/dist/wave-blocked.d.ts.map +1 -1
  263. package/dist/wave-blocked.js +31 -3
  264. package/dist/wave-runtime.mjs +264 -254
  265. package/dist/workspace/check.schema.json +13 -2
  266. package/dist/workspace/checks/ci-branch.d.ts +44 -0
  267. package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
  268. package/dist/workspace/checks/ci-branch.js +133 -0
  269. package/dist/workspace/checks/gate-approval.d.ts +58 -0
  270. package/dist/workspace/checks/gate-approval.d.ts.map +1 -0
  271. package/dist/workspace/checks/gate-approval.js +103 -0
  272. package/dist/workspace/checks/links.d.ts +3 -0
  273. package/dist/workspace/checks/links.d.ts.map +1 -1
  274. package/dist/workspace/checks/links.js +12 -1
  275. package/dist/workspace/checks/org.d.ts +48 -0
  276. package/dist/workspace/checks/org.d.ts.map +1 -0
  277. package/dist/workspace/checks/org.js +156 -0
  278. package/dist/workspace/checks.d.ts +12 -1
  279. package/dist/workspace/checks.d.ts.map +1 -1
  280. package/dist/workspace/checks.js +63 -7
  281. package/dist/workspace/ci-branches.d.ts +16 -0
  282. package/dist/workspace/ci-branches.d.ts.map +1 -1
  283. package/dist/workspace/ci-branches.js +27 -0
  284. package/dist/workspace/ci-green-cli.js +7 -7
  285. package/dist/workspace/ci-green-workflow.d.ts +1 -1
  286. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  287. package/dist/workspace/ci-green-workflow.js +13 -8
  288. package/dist/workspace/ci-green.d.ts +14 -0
  289. package/dist/workspace/ci-green.d.ts.map +1 -1
  290. package/dist/workspace/ci-green.js +44 -11
  291. package/dist/workspace/ci-run.d.ts +9 -1
  292. package/dist/workspace/ci-run.d.ts.map +1 -1
  293. package/dist/workspace/ci-run.js +67 -6
  294. package/dist/workspace/ci-serve.d.ts +9 -0
  295. package/dist/workspace/ci-serve.d.ts.map +1 -1
  296. package/dist/workspace/ci-serve.js +114 -76
  297. package/dist/workspace/compose-graph.d.ts +14 -0
  298. package/dist/workspace/compose-graph.d.ts.map +1 -1
  299. package/dist/workspace/composites.d.ts +4 -1
  300. package/dist/workspace/composites.d.ts.map +1 -1
  301. package/dist/workspace/composites.js +5 -2
  302. package/dist/workspace/composites.schema.json +22 -0
  303. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  304. package/dist/workspace/cross-root-lint.d.ts +56 -0
  305. package/dist/workspace/cross-root-lint.d.ts.map +1 -0
  306. package/dist/workspace/cross-root-lint.js +111 -0
  307. package/dist/workspace/dbt-models-reader.d.ts +44 -0
  308. package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
  309. package/dist/workspace/dbt-models-reader.js +356 -0
  310. package/dist/workspace/dbt-project-common.d.ts +40 -0
  311. package/dist/workspace/dbt-project-common.d.ts.map +1 -0
  312. package/dist/workspace/dbt-project-common.js +106 -0
  313. package/dist/workspace/dbt-project-reader.d.ts +4 -3
  314. package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
  315. package/dist/workspace/dbt-project-reader.js +14 -108
  316. package/dist/workspace/decision-rank.d.ts +26 -0
  317. package/dist/workspace/decision-rank.d.ts.map +1 -0
  318. package/dist/workspace/decision-rank.js +16 -0
  319. package/dist/workspace/declaration.d.ts +50 -2
  320. package/dist/workspace/declaration.d.ts.map +1 -1
  321. package/dist/workspace/declaration.js +57 -11
  322. package/dist/workspace/declaration.schema.json +166 -24
  323. package/dist/workspace/environments.d.ts +26 -2
  324. package/dist/workspace/environments.d.ts.map +1 -1
  325. package/dist/workspace/environments.js +26 -2
  326. package/dist/workspace/exit-cli.d.ts +44 -0
  327. package/dist/workspace/exit-cli.d.ts.map +1 -0
  328. package/dist/workspace/exit-cli.js +198 -0
  329. package/dist/workspace/exit.d.ts +159 -0
  330. package/dist/workspace/exit.d.ts.map +1 -0
  331. package/dist/workspace/exit.js +480 -0
  332. package/dist/workspace/exit.schema.json +257 -0
  333. package/dist/workspace/flyway-dir.d.ts +39 -0
  334. package/dist/workspace/flyway-dir.d.ts.map +1 -0
  335. package/dist/workspace/flyway-dir.js +104 -0
  336. package/dist/workspace/flyway-layout.d.ts +96 -0
  337. package/dist/workspace/flyway-layout.d.ts.map +1 -0
  338. package/dist/workspace/flyway-layout.js +308 -0
  339. package/dist/workspace/graph-cache.d.ts +3 -1
  340. package/dist/workspace/graph-cache.d.ts.map +1 -1
  341. package/dist/workspace/graph-cache.js +12 -1
  342. package/dist/workspace/graph-cli.d.ts.map +1 -1
  343. package/dist/workspace/graph-cli.js +26 -8
  344. package/dist/workspace/graph.schema.json +34 -0
  345. package/dist/workspace/import-fetch.d.ts +38 -0
  346. package/dist/workspace/import-fetch.d.ts.map +1 -0
  347. package/dist/workspace/import-fetch.js +99 -0
  348. package/dist/workspace/import-init.d.ts +26 -0
  349. package/dist/workspace/import-init.d.ts.map +1 -0
  350. package/dist/workspace/import-init.js +130 -0
  351. package/dist/workspace/import-source.d.ts +147 -0
  352. package/dist/workspace/import-source.d.ts.map +1 -0
  353. package/dist/workspace/import-source.js +370 -0
  354. package/dist/workspace/init.d.ts +4 -0
  355. package/dist/workspace/init.d.ts.map +1 -1
  356. package/dist/workspace/init.js +70 -3
  357. package/dist/workspace/intent.d.ts +2 -3
  358. package/dist/workspace/intent.d.ts.map +1 -1
  359. package/dist/workspace/intent.js +5 -9
  360. package/dist/workspace/kind-link-readers.d.ts +59 -2
  361. package/dist/workspace/kind-link-readers.d.ts.map +1 -1
  362. package/dist/workspace/kind-link-readers.js +17 -1
  363. package/dist/workspace/kind-readers.d.ts +6 -0
  364. package/dist/workspace/kind-readers.d.ts.map +1 -1
  365. package/dist/workspace/kind-readers.js +18 -0
  366. package/dist/workspace/kinds.d.ts +90 -3
  367. package/dist/workspace/kinds.d.ts.map +1 -1
  368. package/dist/workspace/kinds.js +161 -7
  369. package/dist/workspace/ledger.schema.json +158 -0
  370. package/dist/workspace/lexicon-outputs.d.ts +20 -1
  371. package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
  372. package/dist/workspace/lexicon-outputs.js +102 -10
  373. package/dist/workspace/lineage-check.d.ts +3 -0
  374. package/dist/workspace/lineage-check.d.ts.map +1 -1
  375. package/dist/workspace/lineage-check.js +23 -4
  376. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  377. package/dist/workspace/lineage-upgrade.js +13 -4
  378. package/dist/workspace/links.d.ts +8 -1
  379. package/dist/workspace/links.d.ts.map +1 -1
  380. package/dist/workspace/links.js +25 -1
  381. package/dist/workspace/ls.d.ts +8 -0
  382. package/dist/workspace/ls.d.ts.map +1 -1
  383. package/dist/workspace/ls.js +7 -1
  384. package/dist/workspace/ls.schema.json +7 -0
  385. package/dist/workspace/member-commands.d.ts.map +1 -1
  386. package/dist/workspace/member-commands.js +54 -5
  387. package/dist/workspace/migrate-links.d.ts +45 -0
  388. package/dist/workspace/migrate-links.d.ts.map +1 -0
  389. package/dist/workspace/migrate-links.js +100 -0
  390. package/dist/workspace/migration-dirs.d.ts +74 -0
  391. package/dist/workspace/migration-dirs.d.ts.map +1 -0
  392. package/dist/workspace/migration-dirs.js +254 -0
  393. package/dist/workspace/new-migrations.d.ts +47 -0
  394. package/dist/workspace/new-migrations.d.ts.map +1 -0
  395. package/dist/workspace/new-migrations.js +91 -0
  396. package/dist/workspace/pr-report.schema.json +46 -0
  397. package/dist/workspace/promote.d.ts +96 -0
  398. package/dist/workspace/promote.d.ts.map +1 -0
  399. package/dist/workspace/promote.js +263 -0
  400. package/dist/workspace/reason-codes.d.ts +21 -0
  401. package/dist/workspace/reason-codes.d.ts.map +1 -1
  402. package/dist/workspace/reason-codes.js +25 -0
  403. package/dist/workspace/records-cli.d.ts +11 -0
  404. package/dist/workspace/records-cli.d.ts.map +1 -1
  405. package/dist/workspace/records-cli.js +57 -5
  406. package/dist/workspace/records.schema.json +5 -0
  407. package/dist/workspace/release-candidates.d.ts +2 -0
  408. package/dist/workspace/release-candidates.d.ts.map +1 -1
  409. package/dist/workspace/release-candidates.js +5 -2
  410. package/dist/workspace/release-train.d.ts +53 -0
  411. package/dist/workspace/release-train.d.ts.map +1 -0
  412. package/dist/workspace/release-train.js +134 -0
  413. package/dist/workspace/release-workflow.d.ts.map +1 -1
  414. package/dist/workspace/release-workflow.js +4 -3
  415. package/dist/workspace/repo-ref.d.ts +45 -0
  416. package/dist/workspace/repo-ref.d.ts.map +1 -0
  417. package/dist/workspace/repo-ref.js +114 -0
  418. package/dist/workspace/run-event.schema.json +2 -2
  419. package/dist/workspace/runs-cli.d.ts +9 -0
  420. package/dist/workspace/runs-cli.d.ts.map +1 -1
  421. package/dist/workspace/runs-cli.js +48 -3
  422. package/dist/workspace/runs.schema.json +7 -0
  423. package/dist/workspace/search-contract.d.ts +1 -1
  424. package/dist/workspace/search-contract.d.ts.map +1 -1
  425. package/dist/workspace/status-decisions.d.ts +37 -0
  426. package/dist/workspace/status-decisions.d.ts.map +1 -0
  427. package/dist/workspace/status-decisions.js +147 -0
  428. package/dist/workspace/status-gates.d.ts +9 -0
  429. package/dist/workspace/status-gates.d.ts.map +1 -1
  430. package/dist/workspace/status.d.ts +4 -0
  431. package/dist/workspace/status.d.ts.map +1 -1
  432. package/dist/workspace/status.js +14 -2
  433. package/dist/workspace/status.schema.json +6 -0
  434. package/dist/workspace/work-cli.d.ts.map +1 -1
  435. package/dist/workspace/work-cli.js +5 -3
  436. package/dist/workspace/work-evidence-ops.d.ts +83 -0
  437. package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
  438. package/dist/workspace/work-evidence-ops.js +169 -0
  439. package/dist/workspace/work-evidence.d.ts +12 -2
  440. package/dist/workspace/work-evidence.d.ts.map +1 -1
  441. package/dist/workspace/work-evidence.js +21 -3
  442. package/dist/workspace/work-evidence.schema.json +49 -2
  443. package/dist/workspace/workspace-gates.d.ts +110 -0
  444. package/dist/workspace/workspace-gates.d.ts.map +1 -0
  445. package/dist/workspace/workspace-gates.js +140 -0
  446. package/dist/workspace/workspace-kinds.schema.json +108 -10
  447. package/dist/workspace/workspace-ledger.d.ts +76 -0
  448. package/dist/workspace/workspace-ledger.d.ts.map +1 -0
  449. package/dist/workspace/workspace-ledger.js +91 -0
  450. package/dist/workspace/workspace-run.d.ts +112 -0
  451. package/dist/workspace/workspace-run.d.ts.map +1 -0
  452. package/dist/workspace/workspace-run.js +432 -0
  453. package/package.json +1 -1
  454. package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  455. package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  456. package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  457. package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  458. package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  459. package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  460. package/src/__golden__/wave-loop/op-cost-member.json +13 -0
  461. package/src/__golden__/wave-loop/op-denied-source.json +16 -0
  462. package/src/__golden__/wave-loop/op-denied.json +16 -0
  463. package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  464. package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  465. package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
  466. package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
  467. package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
  468. package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  469. package/src/__golden__/wave-loop/op-held-on.json +16 -0
  470. package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  471. package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  472. package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  473. package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  474. package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
  475. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  476. package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
  477. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  478. package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
  479. package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
  480. package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  481. package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  482. package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
  483. package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  484. package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  485. package/src/__golden__/wave-loop/op-overridden.json +16 -0
  486. package/src/__golden__/wave-loop/op-per-run.json +20 -0
  487. package/src/__golden__/wave-loop/op-point.json +13 -0
  488. package/src/__golden__/wave-loop/op-pr-review.json +10 -0
  489. package/src/__golden__/wave-loop/op-sealed.json +10 -0
  490. package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
  491. package/src/__golden__/wave-loop/op-spend.json +10 -0
  492. package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
  493. package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
  494. package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
  495. package/src/__golden__/wave-loop/op-wide.json +39 -0
  496. package/src/action-pins.test.ts +78 -0
  497. package/src/action-pins.ts +92 -0
  498. package/src/cli/commands/carve-score-once.test.ts +99 -0
  499. package/src/cli/commands/carve.ts +37 -6
  500. package/src/cli/commands/check-lexicon.test.ts +2 -2
  501. package/src/cli/commands/import-report.ts +71 -0
  502. package/src/cli/commands/import.ts +33 -3
  503. package/src/cli/commands/migrate-dialect.test.ts +145 -0
  504. package/src/cli/commands/migrate.ts +132 -11
  505. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
  506. package/src/cli/handlers/approve.ts +33 -4
  507. package/src/cli/handlers/build.ts +7 -1
  508. package/src/cli/handlers/graph-reader.test.ts +31 -0
  509. package/src/cli/handlers/graph-reader.ts +51 -0
  510. package/src/cli/handlers/graph.ts +14 -0
  511. package/src/cli/handlers/ledger.ts +54 -0
  512. package/src/cli/handlers/migrate.ts +21 -12
  513. package/src/cli/handlers/misc.ts +10 -1
  514. package/src/cli/handlers/pr-env.ts +2 -2
  515. package/src/cli/handlers/pr.ts +176 -37
  516. package/src/cli/handlers/query.ts +16 -4
  517. package/src/cli/handlers/release.ts +6 -3
  518. package/src/cli/handlers/search.ts +13 -34
  519. package/src/cli/main.test.ts +17 -0
  520. package/src/cli/main.ts +87 -14
  521. package/src/cli/parse-args.ts +35 -2
  522. package/src/cli/registry.ts +22 -4
  523. package/src/components/builders.ts +8 -0
  524. package/src/components/capability-plugin-loader.ts +13 -1
  525. package/src/components/capability-plugin.ts +16 -0
  526. package/src/components/pr-env-run-bundle.test.ts +1 -0
  527. package/src/components/pr-hooks.test.ts +73 -5
  528. package/src/components/pr-hooks.ts +95 -5
  529. package/src/components/pr-pipeline.ts +64 -16
  530. package/src/components/pr-policy.test.ts +64 -1
  531. package/src/components/pr-policy.ts +53 -0
  532. package/src/components/pr-run.ts +4 -0
  533. package/src/components/registry.test.ts +1 -1
  534. package/src/components/starter-plugin.ts +6 -0
  535. package/src/components/verbs/index.ts +2 -0
  536. package/src/components/verbs/publish-artifact.test.ts +60 -0
  537. package/src/components/verbs/publish-artifact.ts +122 -0
  538. package/src/components/verbs/publish-image.test.ts +101 -0
  539. package/src/components/verbs/publish-image.ts +265 -0
  540. package/src/config-environments.ts +28 -1
  541. package/src/config.ts +24 -1
  542. package/src/gate-decision-bundle.test.ts +5 -2
  543. package/src/gate-decision-identity.test.ts +20 -0
  544. package/src/gate-decision.ts +46 -3
  545. package/src/graph-ir.ts +27 -0
  546. package/src/graph-walk.test.ts +44 -0
  547. package/src/graph-walk.ts +102 -0
  548. package/src/import/parser.ts +20 -0
  549. package/src/lexicon.ts +98 -6
  550. package/src/lifecycle/gate-ledger.ts +8 -0
  551. package/src/lifecycle/git.ts +2 -2
  552. package/src/lifecycle/ledger-read.test.ts +107 -0
  553. package/src/lifecycle/ledger-read.ts +195 -0
  554. package/src/lifecycle/release-workstation.ts +24 -0
  555. package/src/lifecycle/run-ledger.ts +5 -2
  556. package/src/lint/cross-root.test.ts +122 -0
  557. package/src/lint/discover.ts +3 -1
  558. package/src/lint/post-synth.ts +212 -2
  559. package/src/lint/rules/op/index.ts +2 -0
  560. package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
  561. package/src/op/activities/activity-contracts.ts +5 -2
  562. package/src/op/activities/index.ts +2 -2
  563. package/src/op/activities/review-agent.test.ts +221 -4
  564. package/src/op/activities/review-agent.ts +265 -22
  565. package/src/op/activities/work-evidence.ts +9 -1
  566. package/src/op/builders.ts +9 -5
  567. package/src/op/composites/apply-op.test.ts +108 -0
  568. package/src/op/composites/apply-op.ts +37 -2
  569. package/src/op/gate-approval-at-base.test.ts +97 -0
  570. package/src/op/gate-approval-base.ts +72 -0
  571. package/src/op/gate-approval.test.ts +6 -1
  572. package/src/op/gate-approval.ts +22 -27
  573. package/src/op/gate-name.ts +16 -1
  574. package/src/op/gate-quorum-at-base.test.ts +211 -0
  575. package/src/op/gate-quorum.ts +156 -8
  576. package/src/op/gate-review.ts +14 -0
  577. package/src/op/gate.ts +69 -9
  578. package/src/op/index.ts +3 -2
  579. package/src/op/local-executor.ts +91 -11
  580. package/src/op/op-waves-depends-on.test.ts +81 -0
  581. package/src/op/op-waves-hook-targets.test.ts +4 -2
  582. package/src/op/op-waves-quorum-product.test.ts +158 -0
  583. package/src/op/op-waves-run.ts +288 -14
  584. package/src/op/op-waves.ts +143 -14
  585. package/src/op/runtime.ts +8 -0
  586. package/src/op/schedule-overdue.ts +33 -0
  587. package/src/op/stage-hooks.ts +13 -5
  588. package/src/op/workspace-run-env.ts +29 -0
  589. package/src/pr-forge-bundle.test.ts +2 -2
  590. package/src/pr-forge.test.ts +47 -0
  591. package/src/pr-forge.ts +68 -0
  592. package/src/pr-loop-bundle.test.ts +3 -2
  593. package/src/pr-loop.test.ts +52 -0
  594. package/src/pr-loop.ts +12 -0
  595. package/src/pr-migrations.ts +97 -0
  596. package/src/pr-post-open.e2e.test.ts +36 -0
  597. package/src/pr-post.test.ts +102 -3
  598. package/src/pr-post.ts +90 -2
  599. package/src/read-filter.test.ts +54 -0
  600. package/src/read-filter.ts +193 -0
  601. package/src/run-event.ts +1 -1
  602. package/src/stage-hooks-bundle.test.ts +1 -0
  603. package/src/terraform/carve.ts +52 -15
  604. package/src/terraform/cuts.test.ts +173 -0
  605. package/src/terraform/cuts.ts +188 -0
  606. package/src/terraform/folds.ts +44 -0
  607. package/src/terraform/graph.ts +9 -0
  608. package/src/terraform/radius.test.ts +90 -0
  609. package/src/terraform/reach.test.ts +146 -0
  610. package/src/terraform/reach.ts +247 -0
  611. package/src/terraform/score.ts +27 -29
  612. package/src/terraform/state.ts +3 -1
  613. package/src/terraform/types.ts +8 -0
  614. package/src/wave-blocked.ts +28 -3
  615. package/src/wave-loop-golden.test.ts +4 -3
  616. package/src/workspace/__fixtures__/import/spacelift.json +9 -0
  617. package/src/workspace/__fixtures__/import/tfc.json +13 -0
  618. package/src/workspace/behold-kinds.test.ts +2 -1
  619. package/src/workspace/check-contract.test.ts +8 -6
  620. package/src/workspace/check.schema.json +13 -2
  621. package/src/workspace/checks/ci-branch.test.ts +128 -0
  622. package/src/workspace/checks/ci-branch.ts +141 -0
  623. package/src/workspace/checks/gate-approval.test.ts +77 -0
  624. package/src/workspace/checks/gate-approval.ts +121 -0
  625. package/src/workspace/checks/links.ts +12 -1
  626. package/src/workspace/checks/org.ts +169 -0
  627. package/src/workspace/checks.ts +68 -7
  628. package/src/workspace/ci-branches.ts +31 -0
  629. package/src/workspace/ci-green-branches.test.ts +153 -0
  630. package/src/workspace/ci-green-cli.ts +7 -7
  631. package/src/workspace/ci-green-workflow.test.ts +4 -3
  632. package/src/workspace/ci-green-workflow.ts +13 -9
  633. package/src/workspace/ci-green.ts +49 -12
  634. package/src/workspace/ci-run.test.ts +72 -1
  635. package/src/workspace/ci-run.ts +68 -8
  636. package/src/workspace/ci-serve.test.ts +95 -0
  637. package/src/workspace/ci-serve.ts +106 -65
  638. package/src/workspace/component-environments.test.ts +25 -2
  639. package/src/workspace/compose-graph.ts +9 -2
  640. package/src/workspace/composites.schema.json +22 -0
  641. package/src/workspace/composites.test.ts +39 -8
  642. package/src/workspace/composites.ts +6 -2
  643. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  644. package/src/workspace/cross-root-lint.test.ts +90 -0
  645. package/src/workspace/cross-root-lint.ts +126 -0
  646. package/src/workspace/dbt-kind.test.ts +3 -2
  647. package/src/workspace/dbt-models-reader.ts +362 -0
  648. package/src/workspace/dbt-models.test.ts +248 -0
  649. package/src/workspace/dbt-project-common.ts +113 -0
  650. package/src/workspace/dbt-project-reader.ts +29 -112
  651. package/src/workspace/decision-rank.ts +40 -0
  652. package/src/workspace/declaration.schema.json +166 -24
  653. package/src/workspace/declaration.ts +101 -15
  654. package/src/workspace/environments.ts +40 -4
  655. package/src/workspace/exit-cli.ts +219 -0
  656. package/src/workspace/exit.schema.json +257 -0
  657. package/src/workspace/exit.test.ts +185 -0
  658. package/src/workspace/exit.ts +560 -0
  659. package/src/workspace/flyway-dir.ts +113 -0
  660. package/src/workspace/flyway-layout.ts +337 -0
  661. package/src/workspace/graph-cache.ts +12 -2
  662. package/src/workspace/graph-cli.ts +25 -10
  663. package/src/workspace/graph-contract.test.ts +4 -2
  664. package/src/workspace/graph.schema.json +34 -0
  665. package/src/workspace/identity.test.ts +29 -2
  666. package/src/workspace/import-fetch.ts +151 -0
  667. package/src/workspace/import-init.ts +147 -0
  668. package/src/workspace/import-source.test.ts +183 -0
  669. package/src/workspace/import-source.ts +462 -0
  670. package/src/workspace/init.test.ts +55 -1
  671. package/src/workspace/init.ts +66 -4
  672. package/src/workspace/intent.ts +6 -14
  673. package/src/workspace/kind-link-readers.ts +68 -3
  674. package/src/workspace/kind-readers.test.ts +36 -0
  675. package/src/workspace/kind-readers.ts +18 -1
  676. package/src/workspace/kinds.test.ts +35 -7
  677. package/src/workspace/kinds.ts +212 -11
  678. package/src/workspace/ledger.schema.json +158 -0
  679. package/src/workspace/lexicon-outputs.ts +132 -13
  680. package/src/workspace/lineage-check.ts +30 -7
  681. package/src/workspace/lineage-upgrade.test.ts +20 -0
  682. package/src/workspace/lineage-upgrade.ts +16 -7
  683. package/src/workspace/links.ts +24 -1
  684. package/src/workspace/ls-contract.test.ts +1 -1
  685. package/src/workspace/ls.schema.json +7 -0
  686. package/src/workspace/ls.ts +10 -1
  687. package/src/workspace/member-commands.ts +60 -2
  688. package/src/workspace/member-file-schema.test.ts +124 -0
  689. package/src/workspace/migrate-links.test.ts +99 -0
  690. package/src/workspace/migrate-links.ts +108 -0
  691. package/src/workspace/migration-dirs.test.ts +272 -0
  692. package/src/workspace/migration-dirs.ts +267 -0
  693. package/src/workspace/new-migrations.test.ts +98 -0
  694. package/src/workspace/new-migrations.ts +109 -0
  695. package/src/workspace/org-scope.test.ts +238 -0
  696. package/src/workspace/pr-report.schema.json +46 -0
  697. package/src/workspace/promote.test.ts +114 -0
  698. package/src/workspace/promote.ts +302 -0
  699. package/src/workspace/reason-codes.test.ts +10 -1
  700. package/src/workspace/reason-codes.ts +25 -0
  701. package/src/workspace/records-cli.ts +58 -5
  702. package/src/workspace/records.schema.json +5 -0
  703. package/src/workspace/release-candidates.ts +7 -2
  704. package/src/workspace/release-train.ts +157 -0
  705. package/src/workspace/release-workflow.ts +4 -3
  706. package/src/workspace/repo-ref.ts +113 -0
  707. package/src/workspace/run-event.schema.json +2 -2
  708. package/src/workspace/runs-cli.ts +48 -4
  709. package/src/workspace/runs.schema.json +7 -0
  710. package/src/workspace/status-contract.test.ts +46 -1
  711. package/src/workspace/status-decisions.test.ts +100 -0
  712. package/src/workspace/status-decisions.ts +151 -0
  713. package/src/workspace/status-gates.ts +9 -0
  714. package/src/workspace/status.schema.json +6 -0
  715. package/src/workspace/status.ts +17 -3
  716. package/src/workspace/where-filters.test.ts +39 -0
  717. package/src/workspace/work-cli.ts +5 -3
  718. package/src/workspace/work-evidence-ops.test.ts +176 -0
  719. package/src/workspace/work-evidence-ops.ts +189 -0
  720. package/src/workspace/work-evidence.schema.json +49 -2
  721. package/src/workspace/work-evidence.ts +22 -3
  722. package/src/workspace/workspace-gates.ts +200 -0
  723. package/src/workspace/workspace-kinds.schema.json +108 -10
  724. package/src/workspace/workspace-ledger.ts +130 -0
  725. package/src/workspace/workspace-run.test.ts +322 -0
  726. package/src/workspace/workspace-run.ts +461 -0
@@ -8,6 +8,7 @@ import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSy
8
8
  import { join } from "node:path";
9
9
  import { tmpdir } from "node:os";
10
10
  import { parseDeclaration, WorkspaceReadError } from "../workspace/declaration";
11
+ import { PR_LOOP_HOOK_MOMENTS } from "../op/op-waves";
11
12
  import { prLoopHooksAt, prLoopHooksOf, runPrHooks } from "./pr-hooks";
12
13
  import type { OpWaveExec } from "../op/op-waves-run";
13
14
 
@@ -30,6 +31,12 @@ const declaration = (hooks: unknown, memberHooks?: unknown) => ({
30
31
  });
31
32
 
32
33
  describe("ci.hooks in the declaration (#4288)", () => {
34
+ test("accepts after.gate, and runs it after the plan-side moments (#4323)", () => {
35
+ const d = parseDeclaration(JSON.stringify(declaration({ after: { gate: [{ name: "report", command: ["report"] }] } }, { after: { gate: [{ name: "member-report", command: ["mr"] }] } })), "chant.workspace.json", "0.124.41");
36
+ expect(prLoopHooksOf(d, "infra")["after-gate"]!.map((h) => h.name)).toEqual(["report", "member-report"]);
37
+ expect(PR_LOOP_HOOK_MOMENTS).toContain("after-gate");
38
+ });
39
+
33
40
  test("parses at the top level and on a member; the member's run after the top level's", () => {
34
41
  const d = parseDeclaration(
35
42
  JSON.stringify(declaration({ before: { observe: [{ name: "init", command: ["init"] }] } }, { before: { observe: [{ name: "member-init", command: ["mi"], targets: ["db*"] }] } })),
@@ -43,7 +50,7 @@ describe("ci.hooks in the declaration (#4288)", () => {
43
50
 
44
51
  test("refuses hooks a pull-request job cannot run", () => {
45
52
  const read = (hooks: unknown) => () => parseDeclaration(JSON.stringify(declaration(hooks)), "chant.workspace.json", "0.124.41");
46
- expect(read({ after: { gate: [{ command: ["x"] }] } })).toThrow(WorkspaceReadError);
53
+ expect(read({ before: { plan: [{ command: ["x"], onFailure: "bogus" }] } })).toThrow(WorkspaceReadError);
47
54
  expect(read({ before: { plan: [{ command: [] }] } })).toThrow(/must be a command as argv/);
48
55
  expect(read({ apply: { runBy: "apply" } })).toThrow(/runBy is for an Op waves spec/);
49
56
  });
@@ -84,7 +91,7 @@ describe("prLoopHooksAt (#4288)", () => {
84
91
  });
85
92
 
86
93
  describe("runPrHooks (#4288)", () => {
87
- test("runs a moment for the selected components, writes the runs and plan files, and reports a failure", () => {
94
+ test("runs a moment for the selected components, writes the runs and plan files, and reports a failure", async () => {
88
95
  const cwd = tmp();
89
96
  const calls: Array<{ argv: string; env?: Record<string, string> }> = [];
90
97
  const exec: OpWaveExec = (argv, _dir, env) => {
@@ -95,7 +102,7 @@ describe("runPrHooks (#4288)", () => {
95
102
  base: "b".repeat(40),
96
103
  hooks: { "after-plan": [{ name: "check", command: ["check", "{target}", "{plan}"], perRun: true, targets: ["api", "web"] }] },
97
104
  };
98
- const ran = runPrHooks(read, "after-plan", { op: "pr-7", env: "prod", stage: "plan", dir: ".chant/pr", cwd, exec, log: () => {} }, ["api", "db", "web"], {
105
+ const ran = await runPrHooks(read, "after-plan", { op: "pr-7", env: "prod", stage: "plan", dir: ".chant/pr", cwd, exec, log: () => {} }, ["api", "db", "web"], {
99
106
  members: [{ member: "api", component: "api", planDigest: "sha256:1", status: "planned", counts: { create: 1, update: 0, replace: 0, delete: 0 } }],
100
107
  plans: { api: { api: { file: "plan.bin" } } },
101
108
  });
@@ -110,9 +117,70 @@ describe("runPrHooks (#4288)", () => {
110
117
  expect(ran.failed).toBe("after-plan hook check (web) exited 1");
111
118
  });
112
119
 
113
- test("a moment with no hooks runs nothing and writes nothing", () => {
120
+ test("a moment with no hooks runs nothing and writes nothing", async () => {
114
121
  const cwd = tmp();
115
- const ran = runPrHooks({ base: "b", hooks: {} }, "before-apply", { op: "pr-7", env: "prod", stage: "apply", dir: ".chant/pr", cwd, log: () => {} }, ["api"]);
122
+ const ran = await runPrHooks({ base: "b", hooks: {} }, "before-apply", { op: "pr-7", env: "prod", stage: "apply", dir: ".chant/pr", cwd, log: () => {} }, ["api"]);
116
123
  expect(ran).toEqual({ outcomes: [], holds: [] });
117
124
  });
125
+
126
+ const holdBody = { gate: "db-migrate", digest: "sha256:m1", op: "other-op" };
127
+ /** An exec for a hook that holds on db-migrate until CHANT_HOLD_APPROVED is set. */
128
+ const holdingExec = (cwd: string, calls: Array<Record<string, string> | undefined>): OpWaveExec => (argv, _dir, env) => {
129
+ calls.push(env);
130
+ if (env?.CHANT_HOLD_APPROVED) return 0;
131
+ writeFileSync(join(cwd, env!.CHANT_HOLD!), JSON.stringify(holdBody));
132
+ return argv[0] === "hold" ? 1 : 0;
133
+ };
134
+ const gateHooks = { "after-gate": [{ name: "migrate", command: ["hold"], onFailure: "approve" as const }] };
135
+ const place = (cwd: string, exec: OpWaveExec) => ({ op: "pr-7", env: "prod", stage: "apply" as const, dir: ".chant/pr", cwd, exec, log: () => {} });
136
+
137
+ test("an after-gate hook gets the gate's decision in its environment (#4323)", async () => {
138
+ const cwd = tmp();
139
+ const calls: Array<Record<string, string> | undefined> = [];
140
+ const exec: OpWaveExec = (_argv, _dir, env) => (calls.push(env), 0);
141
+ const read = { base: "b", hooks: { "after-gate": [{ name: "report", command: ["report"] }] } };
142
+ const decision = { CHANT_GATE_STATUS: "approved", CHANT_GATE_OP: "pr-7", CHANT_GATE: "pr-apply", CHANT_GATE_DIGEST: "sha256:p" };
143
+ const ran = await runPrHooks(read, "after-gate", place(cwd, exec), ["api"], undefined, { env: decision });
144
+ expect(calls[0]).toMatchObject({ CHANT_HOOK: "after-gate", ...decision });
145
+ expect(ran.outcomes).toEqual([{ moment: "after-gate", name: "report", exit: 0, outcome: "passed" }]);
146
+ });
147
+
148
+ test("a hold on another gate clears once that gate is approved: the hook runs again with CHANT_HOLD_APPROVED (#4323)", async () => {
149
+ const cwd = tmp();
150
+ const calls: Array<Record<string, string> | undefined> = [];
151
+ let approved = false;
152
+ const asked: string[] = [];
153
+ const settle = async (hold: { request: { gate: string; digest: string; op?: string } }) => {
154
+ asked.push(`${hold.request.op} / ${hold.request.gate} @ ${hold.request.digest}`);
155
+ return approved ? { status: "approved" as const, approvedBy: "alice", approvedAt: "2026-10-23T00:00:00Z" } : { status: "waiting" as const };
156
+ };
157
+ const read = { base: "b", hooks: gateHooks };
158
+ const first = await runPrHooks(read, "after-gate", place(cwd, holdingExec(cwd, calls)), ["api"], undefined, { settle });
159
+ expect(first.failed).toMatch(/^after-gate hook migrate waits on other-op \/ db-migrate, which nobody approved at sha256:m1\. Approve: /);
160
+ expect(first.holds).toEqual([]);
161
+
162
+ approved = true;
163
+ calls.length = 0;
164
+ const second = await runPrHooks(read, "after-gate", place(cwd, holdingExec(cwd, calls)), ["api"], undefined, { settle });
165
+ expect(second.failed).toBeUndefined();
166
+ expect(second.holds).toEqual([]);
167
+ expect(asked).toEqual(["other-op / db-migrate @ sha256:m1", "other-op / db-migrate @ sha256:m1"]);
168
+ expect(JSON.parse(calls[1]!.CHANT_HOLD_APPROVED!)).toEqual({ op: "other-op", gate: "db-migrate", digest: "sha256:m1", approvedBy: "alice", approvedAt: "2026-10-23T00:00:00Z" });
169
+ expect(second.outcomes.map((o) => o.outcome)).toEqual(["held", "passed"]);
170
+ });
171
+
172
+ test("a re-run that holds on a gate already approved fails the stage (#4323)", async () => {
173
+ const cwd = tmp();
174
+ const exec: OpWaveExec = (_argv, _dir, env) => (writeFileSync(join(cwd, env!.CHANT_HOLD!), JSON.stringify(holdBody)), 1);
175
+ const settle = async () => ({ status: "approved" as const, approvedBy: "alice", approvedAt: "t" });
176
+ const ran = await runPrHooks({ base: "b", hooks: gateHooks }, "after-gate", place(cwd, exec), ["api"], undefined, { settle });
177
+ expect(ran.failed).toMatch(/holds again on other-op \/ db-migrate at sha256:m1, which is already approved/);
178
+ });
179
+
180
+ test("without a settler a hold on another gate stays a plain hold", async () => {
181
+ const cwd = tmp();
182
+ const ran = await runPrHooks({ base: "b", hooks: gateHooks }, "after-gate", place(cwd, holdingExec(cwd, [])), ["api"]);
183
+ expect(ran.holds).toEqual(["after-gate hook migrate (on other-op / db-migrate)"]);
184
+ expect(ran.failed).toBeUndefined();
185
+ });
118
186
  });
@@ -16,11 +16,13 @@
16
16
  * component's members and the plans its steps hand the apply.
17
17
  */
18
18
 
19
- import { mkdirSync, writeFileSync } from "node:fs";
19
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
20
20
  import { join, relative, resolve } from "node:path";
21
21
  import type { OpWaveHook, OpWaveHookMoment, OpWaveHooks } from "../op/op-waves";
22
22
  import { PR_LOOP_HOOK_MOMENTS } from "../op/op-waves";
23
- import { runStageHooks, type OpWaveExec, type OpWaveHookRun, type StageHooksRun } from "../op/op-waves-run";
23
+ import { OP_WAVE_MAX_CHAINED_HOLDS, runStageHooks, type OpWaveExec, type OpWaveHookRun, type StageHooksRun } from "../op/op-waves-run";
24
+ import { readHoldRequest, spawnExec, type OpWaveHookOutcome, type OtherGateHold } from "../op/stage-hooks";
25
+ import { approveCommand } from "../op/gate";
24
26
  import { readDeclaration, WorkspaceReadError, type Declaration } from "../workspace/declaration";
25
27
  import { memberAtCwd } from "../workspace/security";
26
28
  import { locateWorkspace } from "../workspace/which-chant";
@@ -101,6 +103,20 @@ export interface PrHookPlace {
101
103
  log?: (line: string) => void;
102
104
  }
103
105
 
106
+ /** How a gate a hook holds the job on stands (#4323). */
107
+ export interface PrHoldSettled {
108
+ status: "approved" | "waiting";
109
+ approvedBy?: string;
110
+ approvedAt?: string;
111
+ }
112
+
113
+ /**
114
+ * Settle a hold on another gate (#4323): an approval of `hold.request.gate`
115
+ * at `hold.request.digest`, else a pending fact for it. The pull-request
116
+ * jobs give this the ledger they decide their own gate by.
117
+ */
118
+ export type PrHoldSettler = (hold: OtherGateHold) => Promise<PrHoldSettled>;
119
+
104
120
  /** What one moment's hooks came to on the pull-request path. */
105
121
  export interface PrHooksRun {
106
122
  outcomes: PrHookOutcome[];
@@ -108,18 +124,27 @@ export interface PrHooksRun {
108
124
  holds: string[];
109
125
  }
110
126
 
127
+ /** The run's options: the gate's decision for `after-gate`, and who settles a hold on another gate. */
128
+ export interface PrHooksRunOptions {
129
+ /** Added to every hook's environment: `after-gate` carries `CHANT_GATE_STATUS`, `CHANT_GATE_OP`, `CHANT_GATE` and `CHANT_GATE_DIGEST`. */
130
+ env?: Record<string, string>;
131
+ /** Without it a hold on another gate is a plain hold. */
132
+ settle?: PrHoldSettler;
133
+ }
134
+
111
135
  /**
112
136
  * Run the hooks of `moment` for `components` (#4288). `plans`, after the
113
137
  * plan, gives each component's members and step plans, written where a
114
138
  * `perRun` hook's `{plan}` points.
115
139
  */
116
- export function runPrHooks(
140
+ export async function runPrHooks(
117
141
  read: PrLoopHooks,
118
142
  moment: OpWaveHookMoment,
119
143
  place: PrHookPlace,
120
144
  components: readonly string[],
121
145
  planned?: { members: readonly PrMember[]; plans: Record<string, Record<string, unknown>> },
122
- ): PrHooksRun {
146
+ options: PrHooksRunOptions = {},
147
+ ): Promise<PrHooksRun> {
123
148
  const hooks = read.hooks[moment];
124
149
  if (!hooks || hooks.length === 0) return { outcomes: [], holds: [] };
125
150
  // Relative to cwd, as an Op waves job's paths are, whether --output was given relative or absolute.
@@ -141,11 +166,18 @@ export function runPrHooks(
141
166
  runs,
142
167
  runsFile,
143
168
  holdFile: join(dir, "hold.json"),
144
- env: { CHANT_ENV: place.env, CHANT_PR_STAGE: place.stage, CHANT_HOOKS_BASE: read.base },
169
+ env: { CHANT_ENV: place.env, CHANT_PR_STAGE: place.stage, CHANT_HOOKS_BASE: read.base, ...options.env },
170
+ keepOtherGates: options.settle !== undefined,
145
171
  cwd: place.cwd,
146
172
  ...(place.exec ? { exec: place.exec } : {}),
147
173
  ...(place.log ? { log: place.log } : {}),
148
174
  });
175
+ const log = place.log ?? ((line: string) => console.error(line));
176
+ const settled = ran.failed || !options.settle ? undefined : await settleHolds(ran.otherGates ?? [], options.settle, place, log);
177
+ if (settled) {
178
+ ran.outcomes.push(...settled.outcomes);
179
+ if (settled.failed) ran.failed = settled.failed;
180
+ }
149
181
  const outcomes = ran.outcomes.map((o): PrHookOutcome => ({
150
182
  moment: o.moment,
151
183
  name: o.name,
@@ -155,3 +187,61 @@ export function runPrHooks(
155
187
  }));
156
188
  return { outcomes, ...(ran.failed ? { failed: ran.failed } : {}), holds: ran.holds };
157
189
  }
190
+
191
+ /**
192
+ * Settle each hook call that held the job on another gate (#4323), as an Op
193
+ * waves job does (`runHooks` in op-waves-run.ts): once the gate is approved
194
+ * the hook runs again with `CHANT_HOLD_APPROVED` (and `CHANT_HOLDS_APPROVED`,
195
+ * the gates approved so far), and it may hold on a further gate, settled in
196
+ * turn, up to {@link OP_WAVE_MAX_CHAINED_HOLDS}. A gate nobody approved fails
197
+ * the stage, with the command that approves it.
198
+ */
199
+ async function settleHolds(holds: readonly OtherGateHold[], settle: PrHoldSettler, place: PrHookPlace, log: (line: string) => void): Promise<{ outcomes: OpWaveHookOutcome[]; failed?: string }> {
200
+ const outcomes: OpWaveHookOutcome[] = [];
201
+ const exec = place.exec ?? spawnExec;
202
+ for (const first of holds) {
203
+ let hold = first;
204
+ const approved: Array<{ op: string; gate: string; digest: string; approvedBy: string; approvedAt: string }> = [];
205
+ for (;;) {
206
+ const op = hold.request.op ?? place.op;
207
+ const held = await settle(hold);
208
+ if (held.status === "waiting") {
209
+ return { outcomes, failed: `${hold.label} waits on ${op} / ${hold.request.gate}, which nobody approved at ${hold.request.digest}. Approve: ${approveCommand(op, hold.request.gate, undefined, hold.request.digest)}` };
210
+ }
211
+ const now = { op, gate: hold.request.gate, digest: hold.request.digest, approvedBy: held.approvedBy ?? "", approvedAt: held.approvedAt ?? "" };
212
+ approved.push(now);
213
+ const holdFile = join(place.cwd, hold.env.CHANT_HOLD!);
214
+ rmSync(holdFile, { force: true });
215
+ let code: number;
216
+ try {
217
+ code = exec(hold.argv, place.cwd, { ...hold.env, CHANT_HOLD_APPROVED: JSON.stringify(now), CHANT_HOLDS_APPROVED: JSON.stringify(approved) });
218
+ } catch (err) {
219
+ log(`${hold.label} did not run: ${err instanceof Error ? err.message : String(err)}`);
220
+ code = 127;
221
+ }
222
+ const target = hold.target ? { target: hold.target } : {};
223
+ if (code === 0) {
224
+ outcomes.push({ moment: hold.moment, name: hold.name, ...target, exit: code, outcome: "passed" });
225
+ log(`${hold.label}: ${now.approvedBy} approved ${op} / ${now.gate} at ${now.digest}, and the hook ran again and passed`);
226
+ break;
227
+ }
228
+ const next = readHoldRequest(holdFile, log, hold.label);
229
+ const nextOp = next ? (next.op ?? place.op) : "";
230
+ const problem = !next
231
+ ? `${hold.label} exited ${code} after ${now.approvedBy} approved ${now.gate}`
232
+ : approved.some((a) => a.op === nextOp && a.gate === next.gate && a.digest === next.digest)
233
+ ? `${hold.label} exited ${code} after ${now.approvedBy} approved ${now.gate}, and holds again on ${nextOp} / ${next.gate} at ${next.digest}, which is already approved`
234
+ : approved.length >= OP_WAVE_MAX_CHAINED_HOLDS
235
+ ? `${hold.label} held the job on ${approved.length} gates in turn and asks for one more; a hook call holds on at most ${OP_WAVE_MAX_CHAINED_HOLDS}`
236
+ : undefined;
237
+ if (problem) {
238
+ outcomes.push({ moment: hold.moment, name: hold.name, ...target, exit: code, outcome: "failed" });
239
+ log(problem);
240
+ return { outcomes, failed: problem };
241
+ }
242
+ outcomes.push({ moment: hold.moment, name: hold.name, ...target, exit: code, outcome: "held" });
243
+ hold = { ...hold, request: next! };
244
+ }
245
+ }
246
+ return { outcomes };
247
+ }
@@ -73,6 +73,10 @@
73
73
  * forge's run as its `runOrigin`. `ci run` runs `command` in the job's own
74
74
  * checkout with the job's environment, less the key. The plan passes
75
75
  * `--no-push`, so it still writes nothing to the remote `chant/lifecycle`.
76
+ * The plan job gets no `CHANT_RUNNER_KEY` (#4328): it runs the pull request's
77
+ * code, and a key in the job's environment is one that code could read and
78
+ * sign with, whatever `ci run` leaves out of its steps. Its record is
79
+ * unsigned.
76
80
  *
77
81
  * In a workspace member (#3465) both commands run in the member's directory
78
82
  * and pass `--member <name>`, so the member's gate (`pr-<number>-<member>`),
@@ -211,6 +215,14 @@ export interface PrLoopPipelineOptions {
211
215
  * ({@link prEnvJobs}). Set from `--pr-env` beside `--pr-loop`. Default false.
212
216
  */
213
217
  prEnvironment?: boolean;
218
+ /**
219
+ * `ci.locks: plan` (#4293, #4327): a `lock` job after the plan that runs
220
+ * `chant lock --from-report`, and an `unlock` job when the pull request
221
+ * closes that runs `chant unlock`. Both check out the target branch's
222
+ * commit, so neither runs the change's code. Set from the declaration at
223
+ * base; GitHub and Forgejo only. Absent: neither job.
224
+ */
225
+ locks?: "plan";
214
226
  /**
215
227
  * The cloud identity a pull request environment's jobs take (#4330), from
216
228
  * `ci.identity.cloud` (`resolvePrEnvCloudIdentity` in ../ci-identity.ts):
@@ -222,6 +234,18 @@ export interface PrLoopPipelineOptions {
222
234
  prEnvIdentity?: CloudTradeSpec;
223
235
  }
224
236
 
237
+ /** The lock job's step names and commands (#4327), shared by the GitHub-shaped generators. */
238
+ export const PR_LOCK_JOB = "lock";
239
+ export const PR_UNLOCK_JOB = "unlock";
240
+
241
+ /** `chant lock --from-report` for the plan's report in {@link PR_LOOP_REPORT_DIR}; reads `PR_NUMBER` and `HEAD_SHA`. */
242
+ export function prLockCommand(forge: ForgeKind): string {
243
+ return ["chant", "lock", "--from-report", `${PR_LOOP_REPORT_DIR}/pr-plan.json`, "--pr", '"$PR_NUMBER"', "--head", '"$HEAD_SHA"', "--stage", "plan", "--forge", forge].join(" ");
244
+ }
245
+
246
+ /** `chant unlock` for the pull request that closed; reads `PR_NUMBER`. */
247
+ export const PR_UNLOCK_COMMAND = 'chant unlock --pr "$PR_NUMBER"';
248
+
225
249
  /** One job's command and the forge values it reads, as environment variables for its step. */
226
250
  export interface PrLoopJob {
227
251
  /** `plan`, `apply` or `record` (#4168). */
@@ -258,8 +282,10 @@ export interface PrLoopJob {
258
282
  * <job> --in-place` with the same environment, gate, forge, events and
259
283
  * report URL, which runs `command` and records the run. `env` is the
260
284
  * job's `env` and, on GitHub and Forgejo, `CHANT_RUNNER_KEY` from the
261
- * repository secret of that name. GitLab hands its CI/CD variables to a
262
- * job itself.
285
+ * repository secret of that name, except for the plan, which runs the
286
+ * pull request's code (#4328). GitLab hands its CI/CD variables to a job
287
+ * itself, so there the key is a protected variable, which a merge request
288
+ * pipeline never gets.
263
289
  */
264
290
  run: { command: string; env: Record<string, string> };
265
291
  }
@@ -354,7 +380,8 @@ export function prLoopJobs(forge: ForgeKind, env: string, options: PrLoopPipelin
354
380
  command: ["chant", "components", "pr-plan", "--base", '"$BASE_SHA"', "--pr", '"$PR_NUMBER"', ...common, ...(options.waveGate ? ["--wave-gate", options.waveGate] : []), "--no-status"].join(" "),
355
381
  env: planEnv,
356
382
  ...build,
357
- run: { command: ciRun("plan").join(" "), env: runEnv(planEnv) },
383
+ // #4328: no runner key either. The step drops it, but the job's environment is the pull request's code's too.
384
+ run: { command: ciRun("plan").join(" "), env: planEnv },
358
385
  },
359
386
  early ? earlyApply : greenApply,
360
387
  {
@@ -372,31 +399,52 @@ export function prLoopJobs(forge: ForgeKind, env: string, options: PrLoopPipelin
372
399
  export const PR_PLAN_NOTE_JOB = "plan-note";
373
400
 
374
401
  /**
375
- * The plan-note job's command and the values it reads (#4295). It runs in a
376
- * fresh job after the plan, at the target branch's commit, and posts the
377
- * note and the `chant/plan` status from the report the plan job handed on,
378
- * with the forge token the plan job never held. `HEAD_SHA` is the commit
379
- * the plan job checked out, `PR_NUMBER` the pull request, and on GitHub and
380
- * Forgejo `PLAN_RESULT` the plan job's result, which fails the status when
381
- * the plan job failed. `BASE_SHA` is the commit the note job checks out on
382
- * GitLab, whose runner checks out the pipeline's own commit.
402
+ * The plan-note job's command and the values it reads (#4295). On GitHub
403
+ * and Forgejo it runs in a fresh job after the plan, at the target branch's
404
+ * commit, and posts the note and the `chant/plan` status from the report
405
+ * the plan job handed on, with the forge token the plan job never held.
406
+ * `HEAD_SHA` is the commit the plan job checked out, `PR_NUMBER` the pull
407
+ * request, and `PLAN_RESULT` the plan job's result, which fails the status
408
+ * when the plan job failed.
409
+ *
410
+ * On GitLab (#4328) it runs `pr-post --open` in a pipeline of the target
411
+ * branch, which GitLab builds from that branch's files and gives the
412
+ * protected `CHANT_FORGE_TOKEN`. A merge request pipeline is built from the
413
+ * merge request's files, so no job in it holds the token. `pr-post --open`
414
+ * asks GitLab for each open merge request's plan job and reads its report
415
+ * from the job's artifacts.
383
416
  */
384
417
  export function prLoopPostJob(forge: ForgeKind, env: string, member?: string): { command: string; env: Record<string, string> } {
385
- const github = forge !== "gitlab";
418
+ if (forge === "gitlab") {
419
+ return {
420
+ command: ["chant", "components", "pr-post", "--open", "--env", env, "--output", PR_LOOP_REPORT_DIR, "--forge", forge, ...(member ? ["--member", member] : [])].join(" "),
421
+ env: {},
422
+ };
423
+ }
386
424
  const command = [
387
425
  "chant", "components", "pr-post", "--pr", '"$PR_NUMBER"', "--head", '"$HEAD_SHA"',
388
426
  "--env", env, "--output", PR_LOOP_REPORT_DIR, "--forge", forge,
389
427
  ...(member ? ["--member", member] : []),
390
- ...(github ? ["--result", '"$PLAN_RESULT"'] : []),
428
+ "--result", '"$PLAN_RESULT"',
391
429
  ].join(" ");
392
430
  return {
393
431
  command,
394
- env: github
395
- ? { PR_NUMBER: "${{ github.event.pull_request.number }}", HEAD_SHA: "${{ github.sha }}", PLAN_RESULT: "${{ needs.plan.result }}", GITHUB_TOKEN: GITHUB_JOB_TOKEN }
396
- : { PR_NUMBER: "$CI_MERGE_REQUEST_IID", HEAD_SHA: "$CI_COMMIT_SHA", BASE_SHA: "$CI_MERGE_REQUEST_DIFF_BASE_SHA" },
432
+ env: { PR_NUMBER: "${{ github.event.pull_request.number }}", HEAD_SHA: "${{ github.sha }}", PLAN_RESULT: "${{ needs.plan.result }}", GITHUB_TOKEN: GITHUB_JOB_TOKEN },
397
433
  };
398
434
  }
399
435
 
436
+ /** The plan job's name in a GitLab merge request pipeline (#3465, #4328): `plan`, or `<member>-plan` in a workspace member's included file. */
437
+ export function gitlabPlanJobName(member?: string): string {
438
+ return member ? `${member}-plan` : "plan";
439
+ }
440
+
441
+ /**
442
+ * The GitLab variable a pipeline schedule (or a person starting a pipeline
443
+ * of the target branch) sets to `1` to post the open merge requests' plan
444
+ * notes (#4328).
445
+ */
446
+ export const GITLAB_PLAN_NOTES_VAR = "CHANT_PLAN_NOTES";
447
+
400
448
  /**
401
449
  * What starts the apply under apply on merge (#4168 step 9): on GitHub the
402
450
  * tick's workflow finishing (`workflow_run`, since tags its token pushes start
@@ -14,7 +14,9 @@ import { composeChangeSet } from "../change-set";
14
14
  import { memoryGateLedgerPort } from "../op/gate";
15
15
  import { overrideDigest } from "../override-gate";
16
16
  import type { GatePolicyExec } from "../gate-policy";
17
- import { decidePrPolicyOverride, prLoopPolicyAt, prPolicyReport, runPrPolicy, spendPrPolicyOverride, type PrPolicyInput } from "./pr-policy";
17
+ import type { FoundComment, MarkerMatch, PrForge } from "../pr-forge";
18
+ import { reviewMarker, reviewNoteBody } from "../op/activities/review-agent";
19
+ import { decidePrPolicyOverride, policyReviewOf, prLoopPolicyAt, prPolicyReport, REVIEW_NOTE_AUTHORS, runPrPolicy, spendPrPolicyOverride, type PrPolicyInput } from "./pr-policy";
18
20
 
19
21
  const scratch: string[] = [];
20
22
  afterAll(() => {
@@ -143,3 +145,64 @@ describe("a denial and its override (#4294)", () => {
143
145
  await expect(spendPrPolicyOverride(port, "pr-7", { by: "github:carol", at: "2026-10-22T12:00:00Z", reason: "ok", digest: "sha256:1" })).rejects.toThrow(/did not reach the remote/);
144
146
  });
145
147
  });
148
+
149
+ describe("input.review: a policy reads the review's risk (#4326)", () => {
150
+ const HEAD = "a".repeat(40);
151
+ const noteBody = (risk: "low" | "high", head = HEAD) =>
152
+ `${reviewMarker("pr-review")}\n\n${reviewNoteBody({ review: `Replaces the bucket.\n\nrisk: ${risk}\n`, exitCode: 0, digest: "sha256:0123456789ab", instructions: "base", head })}`;
153
+
154
+ /** A forge whose comments are `notes`; findComment applies the marker and author rules the real clients do. */
155
+ function forgeWith(notes: Array<{ body: string; author: string }>): PrForge {
156
+ return {
157
+ async findComment(_pr: number, marker: string, opts: MarkerMatch = {}): Promise<FoundComment | undefined> {
158
+ const want = typeof opts.author === "string" ? [opts.author] : (opts.author ?? []);
159
+ const hit = notes.find((n) => n.body.startsWith(marker) && (!opts.author || want.map((w) => w.toLowerCase()).includes(n.author.toLowerCase())));
160
+ return hit ? { id: 1, body: hit.body } : undefined;
161
+ },
162
+ } as unknown as PrForge;
163
+ }
164
+
165
+ test("a note the run's token wrote gives the risk and the head it read", async () => {
166
+ const forge = forgeWith([{ body: noteBody("high"), author: "github-actions[bot]" }]);
167
+ expect(await policyReviewOf(forge, 7)).toEqual({ found: true, risk: "high", head: HEAD });
168
+ });
169
+
170
+ test("a note a person wrote, even with the marker and a low risk, is not the review", async () => {
171
+ const forge = forgeWith([{ body: noteBody("low"), author: "mallory" }]);
172
+ expect(await policyReviewOf(forge, 7)).toMatchObject({ found: false, risk: "unknown", head: null });
173
+ expect(REVIEW_NOTE_AUTHORS).not.toContain("mallory");
174
+ });
175
+
176
+ test("no pull request, no forge and a forge that fails all read as no review, with the reason", async () => {
177
+ expect(await policyReviewOf(forgeWith([]), 7)).toMatchObject({ found: false, reason: expect.stringContaining("no review note") });
178
+ expect(await policyReviewOf(undefined, 7)).toMatchObject({ found: false });
179
+ expect(await policyReviewOf(forgeWith([]), null)).toMatchObject({ found: false });
180
+ const broken = { findComment: async () => { throw new Error("503"); } } as unknown as PrForge;
181
+ expect(await policyReviewOf(broken, 7)).toMatchObject({ found: false, reason: expect.stringContaining("503") });
182
+ });
183
+
184
+ test("a note whose command failed names the risk unknown", async () => {
185
+ const failed = `${reviewMarker("pr-review")}\n\n${reviewNoteBody({ review: "risk: low", exitCode: 2, digest: "sha256:01", instructions: "base", head: HEAD })}`;
186
+ expect(await policyReviewOf(forgeWith([{ body: failed, author: "gitea-actions" }]), 7)).toEqual({ found: true, risk: "unknown", head: HEAD });
187
+ });
188
+
189
+ // A stand-in for `deny[msg] { input.review.risk == "high" }`: it reads the input file the engine was handed, as opa does.
190
+ const denyHighRisk: GatePolicyExec = (argv) => {
191
+ const fed = JSON.parse(readFileSync(argv[argv.indexOf("--input") + 1]!, "utf-8")) as PrPolicyInput;
192
+ const deny = fed.review?.risk === "high" ? ["the review rated this plan high risk"] : [];
193
+ return { status: 0, stdout: opaOut({ deny }), stderr: "" };
194
+ };
195
+
196
+ test("a policy that denies on high risk denies a plan the review rated high, and passes one rated low", async () => {
197
+ const { repo, base } = repoWithPolicy();
198
+ const read = prLoopPolicyAt(join(repo, "infra"), base);
199
+ const high = await policyReviewOf(forgeWith([{ body: noteBody("high"), author: "github-actions[bot]" }]), 7);
200
+ const low = await policyReviewOf(forgeWith([{ body: noteBody("low"), author: "github-actions[bot]" }]), 7);
201
+ const denied = runPrPolicy(read, { ...input(base), review: high }, denyHighRisk)!;
202
+ expect(denied.result).toBe("denied");
203
+ expect(denied.denials).toEqual([{ rule: "main.deny", message: "the review rated this plan high risk" }]);
204
+ expect(runPrPolicy(read, { ...input(base), review: low }, denyHighRisk)!.result).toBe("passed");
205
+ // A plan nobody reviewed is not high risk; a policy that wants a review denies on `found` itself.
206
+ expect(runPrPolicy(read, { ...input(base), review: await policyReviewOf(forgeWith([]), 7) }, denyHighRisk)!.result).toBe("passed");
207
+ });
208
+ });
@@ -20,6 +20,9 @@
20
20
  * no longer applies under the approvals of the merges it carries.
21
21
  * - error: the policy did not run or gave no readable answer. pr-apply
22
22
  * applies nothing.
23
+ *
24
+ * A policy also reads `input.review` (#4326): the risk the pull request's
25
+ * review note names, when the review agent posted one ({@link policyReviewOf}).
23
26
  */
24
27
 
25
28
  import { readDeclaration, WorkspaceReadError } from "../workspace/declaration";
@@ -36,6 +39,8 @@ import {
36
39
  import type { ChangeSetDocument } from "../change-set";
37
40
  import type { GateLedgerPort } from "../op/gate";
38
41
  import type { PrPolicyReport } from "../pr-loop";
42
+ import type { PrForge } from "../pr-forge";
43
+ import { parseReviewResult, REVIEW_MARKER_PREFIX, type ReviewRisk } from "../op/activities/review-agent";
39
44
 
40
45
  /** `ci.policy` as read at base. */
41
46
  export interface PrLoopPolicy {
@@ -92,6 +97,54 @@ export interface PrPolicyInput {
92
97
  head: string;
93
98
  /** The change-set document the gate binds. Its `entries` are every change the plan proposes. */
94
99
  changeSet: ChangeSetDocument;
100
+ /** What the pull request's review note says (#4326). Absent only when the caller did not look. */
101
+ review?: PolicyReview;
102
+ }
103
+
104
+ /**
105
+ * The review agent's finding as a policy reads it, `input.review`. The risk is
106
+ * the model's, so a policy treats it as one signal beside the change set, never
107
+ * as a verdict. `head` is the commit the review read; a policy that cares
108
+ * whether the review is current compares it with `input.head`.
109
+ */
110
+ export interface PolicyReview {
111
+ /** Whether a review note written by the run's own token was found on the pull request. */
112
+ found: boolean;
113
+ /** The risk the review named, or `unknown` when none was found, the command failed or the review gave none. */
114
+ risk: ReviewRisk | "unknown";
115
+ /** The commit the review read, or null when none was found. */
116
+ head: string | null;
117
+ /** Why nothing was found, for the log. Absent when a review was. */
118
+ reason?: string;
119
+ }
120
+
121
+ /**
122
+ * The users the run's own token acts as on GitHub and Forgejo. Only a note one
123
+ * of them wrote counts as the review: a pull request's author can post a
124
+ * comment that starts with the marker, and cannot post as these.
125
+ */
126
+ export const REVIEW_NOTE_AUTHORS: readonly string[] = ["github-actions[bot]", "gitea-actions", "forgejo-actions"];
127
+
128
+ /**
129
+ * The review of pull request `pr`, read from the sticky note the review job
130
+ * posted (`postReviewNote`, ../op/activities/review-agent.ts). A forge that
131
+ * cannot be read, a pull request that is not known and a note nobody trusted
132
+ * all give `found: false`, which a policy sees as `risk: "unknown"`.
133
+ */
134
+ export async function policyReviewOf(forge: PrForge | undefined, pr: number | null): Promise<PolicyReview> {
135
+ const none = (reason: string): PolicyReview => ({ found: false, risk: "unknown", head: null, reason });
136
+ if (!forge) return none("no forge client, so no pull request note was read");
137
+ if (pr === null) return none("no pull request to read a review from");
138
+ let note;
139
+ try {
140
+ note = await forge.findComment(pr, REVIEW_MARKER_PREFIX, { author: REVIEW_NOTE_AUTHORS });
141
+ } catch (err) {
142
+ return none(`could not read the notes of #${pr}: ${err instanceof Error ? err.message : String(err)}`);
143
+ }
144
+ if (!note) return none(`#${pr} has no review note written by the run's token`);
145
+ const result = parseReviewResult(note.body);
146
+ if (!result) return none(`the review note on #${pr} names no head and risk`);
147
+ return { found: true, risk: result.risk, head: result.head };
95
148
  }
96
149
 
97
150
  /** Run the policy over `input`. Undefined when the base sets no policy. */
@@ -99,6 +99,7 @@ import type { CapabilityRegistry, PlannedOutputs } from "./capability";
99
99
  import type { RunProgressEvent } from "./run-progress";
100
100
  import type { RunEventSink } from "../run-event-stream";
101
101
  import { capabilitySteps, planWaveComponent, readDeployedOutputs } from "./wave-plan";
102
+ import type { PrMigrationFile } from "../pr-migrations";
102
103
 
103
104
  export interface PrPlanSetOptions {
104
105
  /** Every component in the project, not the selection: a planned member's dependency outside it is read too (#4104). */
@@ -773,6 +774,8 @@ export interface PrReportInput {
773
774
  waves?: PrWave[];
774
775
  /** The rounds the apply planned after an upstream applied (#3999). */
775
776
  rounds?: PrRound[];
777
+ /** Plan stage (#4407): the new migration files, classified. */
778
+ migrations?: PrMigrationFile[];
776
779
  }
777
780
 
778
781
  /** The report a stage writes. */
@@ -797,6 +800,7 @@ export function prReport(input: PrReportInput): PrReport {
797
800
  ...(input.set.deployedOutputs ? { deployedOutputs: input.set.deployedOutputs } : {}),
798
801
  ...(input.set.plannedOutputs ? { plannedOutputs: input.set.plannedOutputs } : {}),
799
802
  ...(input.rounds && input.rounds.length > 0 ? { rounds: input.rounds } : {}),
803
+ ...(input.migrations && input.migrations.length > 0 ? { migrations: input.migrations } : {}),
800
804
  approval: input.approval,
801
805
  ...(input.waveGate ? { waveGate: input.waveGate } : {}),
802
806
  ...(input.waveGate && input.waves?.length ? { waves: [...input.waves].sort((a, b) => a.wave - b.wave) } : {}),
@@ -99,7 +99,7 @@ describe("createCapabilityRegistry", () => {
99
99
  // for them to live in instead.
100
100
  test("family table lists exactly the agnostic starter families plus the Cloudflare apply leaves (#1293)", () => {
101
101
  expect(Object.keys(STARTER_VERB_FAMILIES).sort()).toEqual(
102
- ["build", "cloudflare", "escapeHatch", "sbom", "secrets", "supplyChainPolicy", "supplyChainSecurity", "waitVerify"].sort(),
102
+ ["build", "cloudflare", "escapeHatch", "publish", "sbom", "secrets", "supplyChainPolicy", "supplyChainSecurity", "waitVerify"].sort(),
103
103
  );
104
104
  });
105
105
 
@@ -32,6 +32,8 @@ import {
32
32
  zipPackageCapability,
33
33
  jvmBuildCapability,
34
34
  generateSbomCapability,
35
+ publishImageCapability,
36
+ publishArtifactCapability,
35
37
  signCapability,
36
38
  attestProvenanceCapability,
37
39
  verifyCapability,
@@ -62,6 +64,8 @@ export const STARTER_VERB_FAMILIES = {
62
64
  // moved to the aws lexicon (#684) alongside the other AWS leaves; core's sbom
63
65
  // family keeps only the agnostic, artifact-type-keyed `generate-sbom`.
64
66
  sbom: ["generate-sbom"],
67
+ // `publish-image` is one agnostic verb (#3142, #3143); each cloud lexicon contributes its registry login as a backend (`CapabilityPlugin.registryBackends`).
68
+ publish: ["publish-image", "publish-artifact"],
65
69
  supplyChainSecurity: ["sign", "attest-provenance", "verify"],
66
70
  supplyChainPolicy: ["scan-vulnerabilities", "vuln-gate"],
67
71
  // Only the *agnostic* wait/verify verbs are in the starter set; the
@@ -93,6 +97,8 @@ function starterCapabilities(): Array<Capability<never, unknown>> {
93
97
  zipPackageCapability,
94
98
  jvmBuildCapability,
95
99
  generateSbomCapability,
100
+ publishImageCapability,
101
+ publishArtifactCapability,
96
102
  signCapability,
97
103
  attestProvenanceCapability,
98
104
  verifyCapability,
@@ -39,6 +39,8 @@
39
39
 
40
40
  export * from "./build-archive";
41
41
  export * from "./build";
42
+ export * from "./publish-image";
43
+ export * from "./publish-artifact";
42
44
  export * from "./bom-writer";
43
45
  export * from "./sbom-generator";
44
46
  export * from "./sbom";