@intentius/chant 0.124.46 → 0.124.48

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 (633) 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 +21 -2
  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/ledger.d.ts +3 -0
  63. package/dist/cli/handlers/ledger.d.ts.map +1 -0
  64. package/dist/cli/handlers/ledger.js +55 -0
  65. package/dist/cli/handlers/migrate.d.ts.map +1 -1
  66. package/dist/cli/handlers/migrate.js +18 -11
  67. package/dist/cli/handlers/misc.d.ts.map +1 -1
  68. package/dist/cli/handlers/misc.js +12 -1
  69. package/dist/cli/handlers/pr-env.js +2 -2
  70. package/dist/cli/handlers/pr.d.ts +15 -1
  71. package/dist/cli/handlers/pr.d.ts.map +1 -1
  72. package/dist/cli/handlers/pr.js +143 -39
  73. package/dist/cli/handlers/query.d.ts +7 -2
  74. package/dist/cli/handlers/query.d.ts.map +1 -1
  75. package/dist/cli/handlers/query.js +16 -4
  76. package/dist/cli/handlers/release.d.ts.map +1 -1
  77. package/dist/cli/handlers/release.js +6 -3
  78. package/dist/cli/handlers/search.d.ts +2 -4
  79. package/dist/cli/handlers/search.d.ts.map +1 -1
  80. package/dist/cli/handlers/search.js +13 -39
  81. package/dist/cli/main.d.ts.map +1 -1
  82. package/dist/cli/main.js +65 -14
  83. package/dist/cli/parse-args.d.ts.map +1 -1
  84. package/dist/cli/parse-args.js +36 -2
  85. package/dist/cli/registry.d.ts +14 -4
  86. package/dist/cli/registry.d.ts.map +1 -1
  87. package/dist/components/builders.d.ts +5 -1
  88. package/dist/components/builders.d.ts.map +1 -1
  89. package/dist/components/builders.js +5 -0
  90. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  91. package/dist/components/capability-plugin-loader.js +16 -1
  92. package/dist/components/capability-plugin.d.ts +14 -0
  93. package/dist/components/capability-plugin.d.ts.map +1 -1
  94. package/dist/components/pr-hooks.d.ts +21 -1
  95. package/dist/components/pr-hooks.d.ts.map +1 -1
  96. package/dist/components/pr-hooks.js +72 -4
  97. package/dist/components/pr-pipeline.d.ts +45 -10
  98. package/dist/components/pr-pipeline.d.ts.map +1 -1
  99. package/dist/components/pr-pipeline.js +47 -14
  100. package/dist/components/pr-policy.d.ts +36 -0
  101. package/dist/components/pr-policy.d.ts.map +1 -1
  102. package/dist/components/pr-policy.js +36 -0
  103. package/dist/components/starter-plugin.d.ts +1 -0
  104. package/dist/components/starter-plugin.d.ts.map +1 -1
  105. package/dist/components/starter-plugin.js +5 -1
  106. package/dist/components/verbs/index.d.ts +2 -0
  107. package/dist/components/verbs/index.d.ts.map +1 -1
  108. package/dist/components/verbs/index.js +2 -0
  109. package/dist/components/verbs/publish-artifact.d.ts +65 -0
  110. package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
  111. package/dist/components/verbs/publish-artifact.js +74 -0
  112. package/dist/components/verbs/publish-image.d.ts +129 -0
  113. package/dist/components/verbs/publish-image.d.ts.map +1 -0
  114. package/dist/components/verbs/publish-image.js +145 -0
  115. package/dist/config-environments.d.ts +20 -0
  116. package/dist/config-environments.d.ts.map +1 -1
  117. package/dist/config-environments.js +12 -0
  118. package/dist/config.d.ts +9 -1
  119. package/dist/config.d.ts.map +1 -1
  120. package/dist/config.js +6 -2
  121. package/dist/gate-decision.d.ts +11 -2
  122. package/dist/gate-decision.d.ts.map +1 -1
  123. package/dist/gate-decision.js +15 -3
  124. package/dist/graph-ir.d.ts +25 -0
  125. package/dist/graph-ir.d.ts.map +1 -1
  126. package/dist/graph-walk.d.ts +66 -0
  127. package/dist/graph-walk.d.ts.map +1 -0
  128. package/dist/graph-walk.js +70 -0
  129. package/dist/import/parser.d.ts +19 -0
  130. package/dist/import/parser.d.ts.map +1 -1
  131. package/dist/lexicon.d.ts +71 -6
  132. package/dist/lexicon.d.ts.map +1 -1
  133. package/dist/lifecycle/gate-ledger.d.ts +11 -0
  134. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  135. package/dist/lifecycle/git.d.ts +2 -5
  136. package/dist/lifecycle/git.d.ts.map +1 -1
  137. package/dist/lifecycle/ledger-read.d.ts +65 -0
  138. package/dist/lifecycle/ledger-read.d.ts.map +1 -0
  139. package/dist/lifecycle/ledger-read.js +160 -0
  140. package/dist/lifecycle/release-workstation.d.ts +7 -0
  141. package/dist/lifecycle/release-workstation.d.ts.map +1 -1
  142. package/dist/lifecycle/release-workstation.js +25 -0
  143. package/dist/lifecycle/run-ledger.d.ts +4 -3
  144. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  145. package/dist/lifecycle/run-ledger.js +1 -0
  146. package/dist/lint/discover.js +3 -1
  147. package/dist/lint/post-synth.d.ts +127 -1
  148. package/dist/lint/post-synth.d.ts.map +1 -1
  149. package/dist/lint/post-synth.js +84 -2
  150. package/dist/lint/rules/op/index.d.ts +1 -0
  151. package/dist/lint/rules/op/index.d.ts.map +1 -1
  152. package/dist/lint/rules/op/index.js +2 -0
  153. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
  154. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
  155. package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
  156. package/dist/op/activities/activity-contracts.d.ts +5 -2
  157. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  158. package/dist/op/activities/activity-contracts.js +5 -2
  159. package/dist/op/activities/index.d.ts +2 -2
  160. package/dist/op/activities/index.d.ts.map +1 -1
  161. package/dist/op/activities/index.js +1 -1
  162. package/dist/op/activities/review-agent.d.ts +90 -4
  163. package/dist/op/activities/review-agent.d.ts.map +1 -1
  164. package/dist/op/activities/review-agent.js +217 -19
  165. package/dist/op/activities/work-evidence.d.ts +8 -1
  166. package/dist/op/activities/work-evidence.d.ts.map +1 -1
  167. package/dist/op/activities/work-evidence.js +1 -0
  168. package/dist/op/builders.d.ts +9 -5
  169. package/dist/op/builders.d.ts.map +1 -1
  170. package/dist/op/builders.js +9 -5
  171. package/dist/op/composites/apply-op.d.ts +29 -2
  172. package/dist/op/composites/apply-op.d.ts.map +1 -1
  173. package/dist/op/composites/apply-op.js +20 -1
  174. package/dist/op/gate-approval.d.ts +11 -5
  175. package/dist/op/gate-approval.d.ts.map +1 -1
  176. package/dist/op/gate-approval.js +11 -6
  177. package/dist/op/gate-quorum.d.ts +100 -3
  178. package/dist/op/gate-quorum.d.ts.map +1 -1
  179. package/dist/op/gate-quorum.js +108 -6
  180. package/dist/op/gate-review.d.ts +9 -0
  181. package/dist/op/gate-review.d.ts.map +1 -1
  182. package/dist/op/gate-review.js +13 -0
  183. package/dist/op/gate.d.ts +44 -3
  184. package/dist/op/gate.d.ts.map +1 -1
  185. package/dist/op/gate.js +25 -4
  186. package/dist/op/index.d.ts +3 -3
  187. package/dist/op/index.d.ts.map +1 -1
  188. package/dist/op/index.js +1 -1
  189. package/dist/op/local-executor.d.ts +38 -3
  190. package/dist/op/local-executor.d.ts.map +1 -1
  191. package/dist/op/local-executor.js +48 -6
  192. package/dist/op/op-waves-run.d.ts +26 -1
  193. package/dist/op/op-waves-run.d.ts.map +1 -1
  194. package/dist/op/op-waves-run.js +259 -13
  195. package/dist/op/op-waves.d.ts +72 -5
  196. package/dist/op/op-waves.d.ts.map +1 -1
  197. package/dist/op/op-waves.js +110 -15
  198. package/dist/op/runtime.d.ts +11 -0
  199. package/dist/op/runtime.d.ts.map +1 -1
  200. package/dist/op/schedule-overdue.d.ts +16 -0
  201. package/dist/op/schedule-overdue.d.ts.map +1 -1
  202. package/dist/op/schedule-overdue.js +36 -0
  203. package/dist/op/stage-hooks.d.ts +9 -2
  204. package/dist/op/stage-hooks.d.ts.map +1 -1
  205. package/dist/op/stage-hooks.js +7 -5
  206. package/dist/op/workspace-run-env.d.ts +22 -0
  207. package/dist/op/workspace-run-env.d.ts.map +1 -0
  208. package/dist/op/workspace-run-env.js +21 -0
  209. package/dist/pr-forge.d.ts +24 -0
  210. package/dist/pr-forge.d.ts.map +1 -1
  211. package/dist/pr-forge.js +47 -0
  212. package/dist/pr-post.d.ts +42 -0
  213. package/dist/pr-post.d.ts.map +1 -1
  214. package/dist/pr-post.js +67 -1
  215. package/dist/read-filter.d.ts +65 -0
  216. package/dist/read-filter.d.ts.map +1 -0
  217. package/dist/read-filter.js +179 -0
  218. package/dist/run-event.d.ts +1 -1
  219. package/dist/run-event.d.ts.map +1 -1
  220. package/dist/terraform/carve.d.ts +24 -3
  221. package/dist/terraform/carve.d.ts.map +1 -1
  222. package/dist/terraform/carve.js +32 -14
  223. package/dist/terraform/cuts.d.ts +97 -0
  224. package/dist/terraform/cuts.d.ts.map +1 -0
  225. package/dist/terraform/cuts.js +138 -0
  226. package/dist/terraform/folds.d.ts +26 -0
  227. package/dist/terraform/folds.d.ts.map +1 -0
  228. package/dist/terraform/folds.js +38 -0
  229. package/dist/terraform/graph.d.ts.map +1 -1
  230. package/dist/terraform/graph.js +10 -0
  231. package/dist/terraform/reach.d.ts +140 -0
  232. package/dist/terraform/reach.d.ts.map +1 -0
  233. package/dist/terraform/reach.js +170 -0
  234. package/dist/terraform/score.d.ts +14 -0
  235. package/dist/terraform/score.d.ts.map +1 -1
  236. package/dist/terraform/score.js +14 -31
  237. package/dist/terraform/state.d.ts.map +1 -1
  238. package/dist/terraform/state.js +4 -2
  239. package/dist/terraform/types.d.ts +8 -0
  240. package/dist/terraform/types.d.ts.map +1 -1
  241. package/dist/wave-blocked.d.ts +10 -1
  242. package/dist/wave-blocked.d.ts.map +1 -1
  243. package/dist/wave-blocked.js +31 -3
  244. package/dist/wave-runtime.mjs +263 -254
  245. package/dist/workspace/check.schema.json +11 -2
  246. package/dist/workspace/checks/ci-branch.d.ts +44 -0
  247. package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
  248. package/dist/workspace/checks/ci-branch.js +133 -0
  249. package/dist/workspace/checks/links.d.ts +3 -0
  250. package/dist/workspace/checks/links.d.ts.map +1 -1
  251. package/dist/workspace/checks/links.js +12 -1
  252. package/dist/workspace/checks/org.d.ts +48 -0
  253. package/dist/workspace/checks/org.d.ts.map +1 -0
  254. package/dist/workspace/checks/org.js +156 -0
  255. package/dist/workspace/checks.d.ts +11 -1
  256. package/dist/workspace/checks.d.ts.map +1 -1
  257. package/dist/workspace/checks.js +59 -7
  258. package/dist/workspace/ci-branches.d.ts +16 -0
  259. package/dist/workspace/ci-branches.d.ts.map +1 -1
  260. package/dist/workspace/ci-branches.js +27 -0
  261. package/dist/workspace/ci-green-cli.js +7 -7
  262. package/dist/workspace/ci-green-workflow.d.ts +1 -1
  263. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  264. package/dist/workspace/ci-green-workflow.js +13 -8
  265. package/dist/workspace/ci-green.d.ts +14 -0
  266. package/dist/workspace/ci-green.d.ts.map +1 -1
  267. package/dist/workspace/ci-green.js +44 -11
  268. package/dist/workspace/ci-run.d.ts +9 -1
  269. package/dist/workspace/ci-run.d.ts.map +1 -1
  270. package/dist/workspace/ci-run.js +67 -6
  271. package/dist/workspace/ci-serve.d.ts +9 -0
  272. package/dist/workspace/ci-serve.d.ts.map +1 -1
  273. package/dist/workspace/ci-serve.js +114 -76
  274. package/dist/workspace/compose-graph.d.ts +14 -0
  275. package/dist/workspace/compose-graph.d.ts.map +1 -1
  276. package/dist/workspace/composites.d.ts +4 -1
  277. package/dist/workspace/composites.d.ts.map +1 -1
  278. package/dist/workspace/composites.js +5 -2
  279. package/dist/workspace/composites.schema.json +22 -0
  280. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  281. package/dist/workspace/cross-root-lint.d.ts +56 -0
  282. package/dist/workspace/cross-root-lint.d.ts.map +1 -0
  283. package/dist/workspace/cross-root-lint.js +111 -0
  284. package/dist/workspace/dbt-models-reader.d.ts +44 -0
  285. package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
  286. package/dist/workspace/dbt-models-reader.js +356 -0
  287. package/dist/workspace/dbt-project-common.d.ts +40 -0
  288. package/dist/workspace/dbt-project-common.d.ts.map +1 -0
  289. package/dist/workspace/dbt-project-common.js +106 -0
  290. package/dist/workspace/dbt-project-reader.d.ts +4 -3
  291. package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
  292. package/dist/workspace/dbt-project-reader.js +14 -108
  293. package/dist/workspace/decision-rank.d.ts +26 -0
  294. package/dist/workspace/decision-rank.d.ts.map +1 -0
  295. package/dist/workspace/decision-rank.js +16 -0
  296. package/dist/workspace/declaration.d.ts +43 -2
  297. package/dist/workspace/declaration.d.ts.map +1 -1
  298. package/dist/workspace/declaration.js +45 -11
  299. package/dist/workspace/declaration.schema.json +133 -24
  300. package/dist/workspace/environments.d.ts +26 -2
  301. package/dist/workspace/environments.d.ts.map +1 -1
  302. package/dist/workspace/environments.js +26 -2
  303. package/dist/workspace/flyway-dir.d.ts +39 -0
  304. package/dist/workspace/flyway-dir.d.ts.map +1 -0
  305. package/dist/workspace/flyway-dir.js +104 -0
  306. package/dist/workspace/flyway-layout.d.ts +96 -0
  307. package/dist/workspace/flyway-layout.d.ts.map +1 -0
  308. package/dist/workspace/flyway-layout.js +308 -0
  309. package/dist/workspace/graph-cli.d.ts.map +1 -1
  310. package/dist/workspace/graph-cli.js +26 -8
  311. package/dist/workspace/graph.schema.json +34 -0
  312. package/dist/workspace/intent.d.ts +2 -3
  313. package/dist/workspace/intent.d.ts.map +1 -1
  314. package/dist/workspace/intent.js +5 -9
  315. package/dist/workspace/kind-link-readers.d.ts +59 -2
  316. package/dist/workspace/kind-link-readers.d.ts.map +1 -1
  317. package/dist/workspace/kind-link-readers.js +17 -1
  318. package/dist/workspace/kinds.d.ts +66 -2
  319. package/dist/workspace/kinds.d.ts.map +1 -1
  320. package/dist/workspace/kinds.js +98 -4
  321. package/dist/workspace/ledger.schema.json +158 -0
  322. package/dist/workspace/lexicon-outputs.d.ts +20 -1
  323. package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
  324. package/dist/workspace/lexicon-outputs.js +102 -10
  325. package/dist/workspace/lineage-check.d.ts +3 -0
  326. package/dist/workspace/lineage-check.d.ts.map +1 -1
  327. package/dist/workspace/lineage-check.js +23 -4
  328. package/dist/workspace/links.d.ts +8 -1
  329. package/dist/workspace/links.d.ts.map +1 -1
  330. package/dist/workspace/links.js +25 -1
  331. package/dist/workspace/ls.d.ts +8 -0
  332. package/dist/workspace/ls.d.ts.map +1 -1
  333. package/dist/workspace/ls.js +7 -1
  334. package/dist/workspace/ls.schema.json +7 -0
  335. package/dist/workspace/member-commands.d.ts.map +1 -1
  336. package/dist/workspace/member-commands.js +54 -5
  337. package/dist/workspace/migrate-links.d.ts +45 -0
  338. package/dist/workspace/migrate-links.d.ts.map +1 -0
  339. package/dist/workspace/migrate-links.js +100 -0
  340. package/dist/workspace/migration-dirs.d.ts +74 -0
  341. package/dist/workspace/migration-dirs.d.ts.map +1 -0
  342. package/dist/workspace/migration-dirs.js +254 -0
  343. package/dist/workspace/reason-codes.d.ts +8 -0
  344. package/dist/workspace/reason-codes.d.ts.map +1 -1
  345. package/dist/workspace/reason-codes.js +11 -0
  346. package/dist/workspace/records-cli.d.ts +11 -0
  347. package/dist/workspace/records-cli.d.ts.map +1 -1
  348. package/dist/workspace/records-cli.js +57 -5
  349. package/dist/workspace/records.schema.json +5 -0
  350. package/dist/workspace/release-candidates.d.ts +2 -0
  351. package/dist/workspace/release-candidates.d.ts.map +1 -1
  352. package/dist/workspace/release-candidates.js +5 -2
  353. package/dist/workspace/release-train.d.ts +53 -0
  354. package/dist/workspace/release-train.d.ts.map +1 -0
  355. package/dist/workspace/release-train.js +134 -0
  356. package/dist/workspace/release-workflow.d.ts.map +1 -1
  357. package/dist/workspace/release-workflow.js +4 -3
  358. package/dist/workspace/repo-ref.d.ts +45 -0
  359. package/dist/workspace/repo-ref.d.ts.map +1 -0
  360. package/dist/workspace/repo-ref.js +114 -0
  361. package/dist/workspace/run-event.schema.json +2 -2
  362. package/dist/workspace/runs-cli.d.ts +9 -0
  363. package/dist/workspace/runs-cli.d.ts.map +1 -1
  364. package/dist/workspace/runs-cli.js +48 -3
  365. package/dist/workspace/runs.schema.json +7 -0
  366. package/dist/workspace/search-contract.d.ts +1 -1
  367. package/dist/workspace/search-contract.d.ts.map +1 -1
  368. package/dist/workspace/status-decisions.d.ts +37 -0
  369. package/dist/workspace/status-decisions.d.ts.map +1 -0
  370. package/dist/workspace/status-decisions.js +147 -0
  371. package/dist/workspace/status-gates.d.ts +9 -0
  372. package/dist/workspace/status-gates.d.ts.map +1 -1
  373. package/dist/workspace/status.d.ts +4 -0
  374. package/dist/workspace/status.d.ts.map +1 -1
  375. package/dist/workspace/status.js +14 -2
  376. package/dist/workspace/status.schema.json +6 -0
  377. package/dist/workspace/work-cli.d.ts.map +1 -1
  378. package/dist/workspace/work-cli.js +5 -3
  379. package/dist/workspace/work-evidence-ops.d.ts +83 -0
  380. package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
  381. package/dist/workspace/work-evidence-ops.js +169 -0
  382. package/dist/workspace/work-evidence.d.ts +12 -2
  383. package/dist/workspace/work-evidence.d.ts.map +1 -1
  384. package/dist/workspace/work-evidence.js +21 -3
  385. package/dist/workspace/work-evidence.schema.json +49 -2
  386. package/dist/workspace/workspace-gates.d.ts +110 -0
  387. package/dist/workspace/workspace-gates.d.ts.map +1 -0
  388. package/dist/workspace/workspace-gates.js +140 -0
  389. package/dist/workspace/workspace-kinds.schema.json +58 -3
  390. package/dist/workspace/workspace-ledger.d.ts +76 -0
  391. package/dist/workspace/workspace-ledger.d.ts.map +1 -0
  392. package/dist/workspace/workspace-ledger.js +91 -0
  393. package/dist/workspace/workspace-run.d.ts +112 -0
  394. package/dist/workspace/workspace-run.d.ts.map +1 -0
  395. package/dist/workspace/workspace-run.js +432 -0
  396. package/package.json +1 -1
  397. package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  398. package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  399. package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  400. package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  401. package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  402. package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  403. package/src/__golden__/wave-loop/op-cost-member.json +13 -0
  404. package/src/__golden__/wave-loop/op-denied-source.json +16 -0
  405. package/src/__golden__/wave-loop/op-denied.json +16 -0
  406. package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  407. package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  408. package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
  409. package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
  410. package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
  411. package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  412. package/src/__golden__/wave-loop/op-held-on.json +16 -0
  413. package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  414. package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  415. package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  416. package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  417. package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
  418. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  419. package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
  420. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  421. package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
  422. package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
  423. package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  424. package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  425. package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
  426. package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  427. package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  428. package/src/__golden__/wave-loop/op-overridden.json +16 -0
  429. package/src/__golden__/wave-loop/op-per-run.json +20 -0
  430. package/src/__golden__/wave-loop/op-point.json +13 -0
  431. package/src/__golden__/wave-loop/op-pr-review.json +10 -0
  432. package/src/__golden__/wave-loop/op-sealed.json +10 -0
  433. package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
  434. package/src/__golden__/wave-loop/op-spend.json +10 -0
  435. package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
  436. package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
  437. package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
  438. package/src/__golden__/wave-loop/op-wide.json +39 -0
  439. package/src/action-pins.test.ts +78 -0
  440. package/src/action-pins.ts +92 -0
  441. package/src/cli/commands/carve-score-once.test.ts +99 -0
  442. package/src/cli/commands/carve.ts +37 -6
  443. package/src/cli/commands/check-lexicon.test.ts +2 -2
  444. package/src/cli/commands/import-report.ts +71 -0
  445. package/src/cli/commands/import.ts +33 -3
  446. package/src/cli/commands/migrate-dialect.test.ts +145 -0
  447. package/src/cli/commands/migrate.ts +132 -11
  448. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
  449. package/src/cli/handlers/approve.ts +23 -2
  450. package/src/cli/handlers/build.ts +7 -1
  451. package/src/cli/handlers/ledger.ts +54 -0
  452. package/src/cli/handlers/migrate.ts +21 -12
  453. package/src/cli/handlers/misc.ts +10 -1
  454. package/src/cli/handlers/pr-env.ts +2 -2
  455. package/src/cli/handlers/pr.ts +138 -36
  456. package/src/cli/handlers/query.ts +16 -4
  457. package/src/cli/handlers/release.ts +6 -3
  458. package/src/cli/handlers/search.ts +13 -34
  459. package/src/cli/main.test.ts +15 -0
  460. package/src/cli/main.ts +65 -14
  461. package/src/cli/parse-args.ts +27 -2
  462. package/src/cli/registry.ts +14 -4
  463. package/src/components/builders.ts +8 -0
  464. package/src/components/capability-plugin-loader.ts +13 -1
  465. package/src/components/capability-plugin.ts +16 -0
  466. package/src/components/pr-hooks.test.ts +73 -5
  467. package/src/components/pr-hooks.ts +95 -5
  468. package/src/components/pr-pipeline.ts +64 -16
  469. package/src/components/pr-policy.test.ts +64 -1
  470. package/src/components/pr-policy.ts +53 -0
  471. package/src/components/registry.test.ts +1 -1
  472. package/src/components/starter-plugin.ts +6 -0
  473. package/src/components/verbs/index.ts +2 -0
  474. package/src/components/verbs/publish-artifact.test.ts +60 -0
  475. package/src/components/verbs/publish-artifact.ts +122 -0
  476. package/src/components/verbs/publish-image.test.ts +101 -0
  477. package/src/components/verbs/publish-image.ts +265 -0
  478. package/src/config-environments.ts +28 -1
  479. package/src/config.ts +8 -1
  480. package/src/gate-decision-bundle.test.ts +1 -0
  481. package/src/gate-decision-identity.test.ts +9 -0
  482. package/src/gate-decision.ts +26 -3
  483. package/src/graph-ir.ts +27 -0
  484. package/src/graph-walk.test.ts +44 -0
  485. package/src/graph-walk.ts +102 -0
  486. package/src/import/parser.ts +20 -0
  487. package/src/lexicon.ts +74 -6
  488. package/src/lifecycle/gate-ledger.ts +8 -0
  489. package/src/lifecycle/git.ts +2 -2
  490. package/src/lifecycle/ledger-read.test.ts +107 -0
  491. package/src/lifecycle/ledger-read.ts +195 -0
  492. package/src/lifecycle/release-workstation.ts +24 -0
  493. package/src/lifecycle/run-ledger.ts +5 -2
  494. package/src/lint/cross-root.test.ts +122 -0
  495. package/src/lint/discover.ts +3 -1
  496. package/src/lint/post-synth.ts +212 -2
  497. package/src/lint/rules/op/index.ts +2 -0
  498. package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
  499. package/src/op/activities/activity-contracts.ts +5 -2
  500. package/src/op/activities/index.ts +2 -2
  501. package/src/op/activities/review-agent.test.ts +221 -4
  502. package/src/op/activities/review-agent.ts +265 -22
  503. package/src/op/activities/work-evidence.ts +9 -1
  504. package/src/op/builders.ts +9 -5
  505. package/src/op/composites/apply-op.test.ts +108 -0
  506. package/src/op/composites/apply-op.ts +37 -2
  507. package/src/op/gate-approval.test.ts +6 -1
  508. package/src/op/gate-approval.ts +12 -6
  509. package/src/op/gate-quorum-at-base.test.ts +211 -0
  510. package/src/op/gate-quorum.ts +156 -8
  511. package/src/op/gate-review.ts +14 -0
  512. package/src/op/gate.ts +67 -8
  513. package/src/op/index.ts +3 -2
  514. package/src/op/local-executor.ts +69 -9
  515. package/src/op/op-waves-depends-on.test.ts +81 -0
  516. package/src/op/op-waves-hook-targets.test.ts +4 -2
  517. package/src/op/op-waves-quorum-product.test.ts +158 -0
  518. package/src/op/op-waves-run.ts +288 -14
  519. package/src/op/op-waves.ts +143 -14
  520. package/src/op/runtime.ts +8 -0
  521. package/src/op/schedule-overdue.ts +33 -0
  522. package/src/op/stage-hooks.ts +13 -5
  523. package/src/op/workspace-run-env.ts +29 -0
  524. package/src/pr-forge-bundle.test.ts +2 -2
  525. package/src/pr-forge.test.ts +47 -0
  526. package/src/pr-forge.ts +68 -0
  527. package/src/pr-post-open.e2e.test.ts +36 -0
  528. package/src/pr-post.test.ts +102 -3
  529. package/src/pr-post.ts +89 -1
  530. package/src/read-filter.test.ts +54 -0
  531. package/src/read-filter.ts +193 -0
  532. package/src/run-event.ts +1 -1
  533. package/src/stage-hooks-bundle.test.ts +1 -0
  534. package/src/terraform/carve.ts +52 -15
  535. package/src/terraform/cuts.test.ts +173 -0
  536. package/src/terraform/cuts.ts +188 -0
  537. package/src/terraform/folds.ts +44 -0
  538. package/src/terraform/graph.ts +9 -0
  539. package/src/terraform/radius.test.ts +90 -0
  540. package/src/terraform/reach.test.ts +146 -0
  541. package/src/terraform/reach.ts +247 -0
  542. package/src/terraform/score.ts +27 -29
  543. package/src/terraform/state.ts +3 -1
  544. package/src/terraform/types.ts +8 -0
  545. package/src/wave-blocked.ts +28 -3
  546. package/src/wave-loop-golden.test.ts +4 -3
  547. package/src/workspace/behold-kinds.test.ts +1 -1
  548. package/src/workspace/check-contract.test.ts +7 -6
  549. package/src/workspace/check.schema.json +11 -2
  550. package/src/workspace/checks/ci-branch.test.ts +128 -0
  551. package/src/workspace/checks/ci-branch.ts +141 -0
  552. package/src/workspace/checks/links.ts +12 -1
  553. package/src/workspace/checks/org.ts +169 -0
  554. package/src/workspace/checks.ts +64 -7
  555. package/src/workspace/ci-branches.ts +31 -0
  556. package/src/workspace/ci-green-branches.test.ts +153 -0
  557. package/src/workspace/ci-green-cli.ts +7 -7
  558. package/src/workspace/ci-green-workflow.test.ts +4 -3
  559. package/src/workspace/ci-green-workflow.ts +13 -9
  560. package/src/workspace/ci-green.ts +49 -12
  561. package/src/workspace/ci-run.test.ts +72 -1
  562. package/src/workspace/ci-run.ts +68 -8
  563. package/src/workspace/ci-serve.test.ts +95 -0
  564. package/src/workspace/ci-serve.ts +106 -65
  565. package/src/workspace/component-environments.test.ts +25 -2
  566. package/src/workspace/compose-graph.ts +9 -2
  567. package/src/workspace/composites.schema.json +22 -0
  568. package/src/workspace/composites.test.ts +39 -8
  569. package/src/workspace/composites.ts +6 -2
  570. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  571. package/src/workspace/cross-root-lint.test.ts +90 -0
  572. package/src/workspace/cross-root-lint.ts +126 -0
  573. package/src/workspace/dbt-kind.test.ts +3 -2
  574. package/src/workspace/dbt-models-reader.ts +362 -0
  575. package/src/workspace/dbt-models.test.ts +248 -0
  576. package/src/workspace/dbt-project-common.ts +113 -0
  577. package/src/workspace/dbt-project-reader.ts +29 -112
  578. package/src/workspace/decision-rank.ts +40 -0
  579. package/src/workspace/declaration.schema.json +133 -24
  580. package/src/workspace/declaration.ts +84 -15
  581. package/src/workspace/environments.ts +40 -4
  582. package/src/workspace/flyway-dir.ts +113 -0
  583. package/src/workspace/flyway-layout.ts +337 -0
  584. package/src/workspace/graph-cli.ts +25 -10
  585. package/src/workspace/graph-contract.test.ts +4 -2
  586. package/src/workspace/graph.schema.json +34 -0
  587. package/src/workspace/identity.test.ts +14 -2
  588. package/src/workspace/intent.ts +6 -14
  589. package/src/workspace/kind-link-readers.ts +68 -3
  590. package/src/workspace/kinds.test.ts +12 -7
  591. package/src/workspace/kinds.ts +132 -7
  592. package/src/workspace/ledger.schema.json +158 -0
  593. package/src/workspace/lexicon-outputs.ts +132 -13
  594. package/src/workspace/lineage-check.ts +30 -7
  595. package/src/workspace/links.ts +24 -1
  596. package/src/workspace/ls-contract.test.ts +1 -1
  597. package/src/workspace/ls.schema.json +7 -0
  598. package/src/workspace/ls.ts +10 -1
  599. package/src/workspace/member-commands.ts +60 -2
  600. package/src/workspace/member-file-schema.test.ts +124 -0
  601. package/src/workspace/migrate-links.test.ts +99 -0
  602. package/src/workspace/migrate-links.ts +108 -0
  603. package/src/workspace/migration-dirs.test.ts +272 -0
  604. package/src/workspace/migration-dirs.ts +267 -0
  605. package/src/workspace/org-scope.test.ts +238 -0
  606. package/src/workspace/reason-codes.test.ts +6 -1
  607. package/src/workspace/reason-codes.ts +11 -0
  608. package/src/workspace/records-cli.ts +58 -5
  609. package/src/workspace/records.schema.json +5 -0
  610. package/src/workspace/release-candidates.ts +7 -2
  611. package/src/workspace/release-train.ts +157 -0
  612. package/src/workspace/release-workflow.ts +4 -3
  613. package/src/workspace/repo-ref.ts +113 -0
  614. package/src/workspace/run-event.schema.json +2 -2
  615. package/src/workspace/runs-cli.ts +48 -4
  616. package/src/workspace/runs.schema.json +7 -0
  617. package/src/workspace/status-contract.test.ts +46 -1
  618. package/src/workspace/status-decisions.test.ts +100 -0
  619. package/src/workspace/status-decisions.ts +151 -0
  620. package/src/workspace/status-gates.ts +9 -0
  621. package/src/workspace/status.schema.json +6 -0
  622. package/src/workspace/status.ts +17 -3
  623. package/src/workspace/where-filters.test.ts +39 -0
  624. package/src/workspace/work-cli.ts +5 -3
  625. package/src/workspace/work-evidence-ops.test.ts +176 -0
  626. package/src/workspace/work-evidence-ops.ts +189 -0
  627. package/src/workspace/work-evidence.schema.json +49 -2
  628. package/src/workspace/work-evidence.ts +22 -3
  629. package/src/workspace/workspace-gates.ts +200 -0
  630. package/src/workspace/workspace-kinds.schema.json +58 -3
  631. package/src/workspace/workspace-ledger.ts +130 -0
  632. package/src/workspace/workspace-run.test.ts +322 -0
  633. package/src/workspace/workspace-run.ts +461 -0
@@ -17,8 +17,16 @@
17
17
  * (`shares` > 1) is a deciding job that plans every run and decides the gate,
18
18
  * plus `shares` jobs that each apply a slice of the runs and refuse a run
19
19
  * whose plan moved since the decision. Each wave's jobs need the previous
20
- * wave's last jobs, so a wave that waits (exit 3) or fails stops every later
21
- * wave.
20
+ * wave's last jobs, so a wave that waits (exit 3) stops every later wave.
21
+ *
22
+ * ## A failed run blocks its dependents (#4322)
23
+ *
24
+ * A run's `dependsOn` names the runs it needs applied first. Each job writes
25
+ * an outcome file ({@link OpWaveJob.outcome}, `../wave-blocked.ts`) and the
26
+ * next wave's jobs read every earlier one, so a run that failed blocks only
27
+ * the runs that depend on it, in its own wave and later ones, and the rest
28
+ * apply. The generated pipelines run a wave's jobs after a failure in the
29
+ * wave before; a job exits 1 only for its own failed or blocked runs.
22
30
  *
23
31
  * ## The gate
24
32
  *
@@ -51,6 +59,13 @@
51
59
  * in `.chant/trust.json` at base), one of them not the author with
52
60
  * `notAuthor`. See `./gate-quorum.ts`.
53
61
  *
62
+ * Two more settings feed those rules, read at base with them (#4333):
63
+ * `approvals.reviewLedger` names the op whose ledger holds the reviews a
64
+ * product records itself under `pr-review` (terragucci's `tf-apply`), and
65
+ * `approvals.involved` names the accounts an agent acts as and, with
66
+ * `exclude`, leaves an approval by anyone involved in the change out of
67
+ * every rule.
68
+ *
54
69
  * ## Wave-level commands and hooks (#3762)
55
70
  *
56
71
  * A product whose tool plans a whole wave in one call (Terragrunt's `run
@@ -81,6 +96,7 @@ import { gatePointProblems, type GatePoint } from "./gate-point";
81
96
  import { quorumProblems, type GateQuorumRule } from "./gate-quorum";
82
97
  import { notifyConfigProblem, type NotifyConfig } from "../notify";
83
98
  import { WAVE_GATE_NAME_PATTERN, waveGateNameFrom, waveShare } from "../wave-layout";
99
+ import { waveOutcomeFileName } from "../wave-blocked";
84
100
 
85
101
  /**
86
102
  * The variables a wave job reads its notify secrets from, for the lexicons
@@ -217,6 +233,15 @@ export interface OpWaveRun {
217
233
  target: string;
218
234
  /** The Op to run. Default: the spec's `op`. */
219
235
  op?: string;
236
+ /**
237
+ * The runs this one needs applied first (#4322), by target: a run of an
238
+ * earlier wave (the latest earlier wave that has the target), or one listed
239
+ * before it in its own wave. A run that failed, or was blocked, blocks this
240
+ * one, which then is neither planned nor applied; a run with no failed
241
+ * dependency applies whatever else failed. Within its own wave only when
242
+ * one job applies the wave (no `shares`, no `jobs`).
243
+ */
244
+ dependsOn?: string[];
220
245
  }
221
246
 
222
247
  /**
@@ -505,6 +530,48 @@ export interface OpWavesApprovals {
505
530
  * author from that pull request. See `./gate-quorum.ts`.
506
531
  */
507
532
  quorum?: GateQuorumRule[];
533
+ /**
534
+ * The op whose ledger (`_gates/<op>.jsonl` on `chant/lifecycle`) holds the
535
+ * reviews a product records itself (#4333): lines with `via: "pr-review"`,
536
+ * the reviewed `head` and the approving `reviewers` by forge login, as
537
+ * terragucci writes them under `tf-apply`. Each reviewer counts toward
538
+ * `quorum` as one approver of the line's digest, and under `pr-review` is
539
+ * proven, as a review chant reads from the forge is. Read at the base
540
+ * commit, so a change cannot name a ledger its own wave is judged by.
541
+ */
542
+ reviewLedger?: string;
543
+ /** Who else was involved in a change, for `quorum` (#4333). Read at the base commit. */
544
+ involved?: OpWavesInvolved;
545
+ }
546
+
547
+ /**
548
+ * Who was involved in a change besides its pull request's author (#4333):
549
+ * each login a `Requested-by:` trailer names on the commits after base, and
550
+ * the accounts listed here. `notAuthor` is met only by a proven approver
551
+ * none of them is.
552
+ */
553
+ export interface OpWavesInvolved {
554
+ /** The accounts an agent acts as, as principals (`github:tg-bot`) or logins: a change's author can post a review as the agent with its token. */
555
+ agents?: string[];
556
+ /** An approval by anyone involved counts toward no rule, not only toward `notAuthor`. Default false. */
557
+ exclude?: boolean;
558
+ }
559
+
560
+ /** An op name a ledger file is named by (`_gates/<op>.jsonl`). */
561
+ export const OP_LEDGER_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
562
+
563
+ /** Everything wrong with `approvals.involved` (#4333), one line each. */
564
+ export function opWavesInvolvedProblems(value: unknown): string[] {
565
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return ["`approvals.involved` must be { \"agents\"?: [...], \"exclude\"?: true | false }"];
566
+ const problems: string[] = [];
567
+ for (const [key, v] of Object.entries(value as Record<string, unknown>)) {
568
+ if (key === "agents") {
569
+ if (!Array.isArray(v) || v.length === 0 || v.some((a) => typeof a !== "string" || a.trim() === "")) problems.push("`approvals.involved.agents` must be a non-empty list of principals or logins");
570
+ } else if (key === "exclude") {
571
+ if (typeof v !== "boolean") problems.push("`approvals.involved.exclude` must be true or false");
572
+ } else problems.push(`\`approvals.involved.${key}\` is not a setting; use agents or exclude`);
573
+ }
574
+ return problems;
508
575
  }
509
576
 
510
577
  /** The scheduled resume job of an Op waves pipeline (#3683). */
@@ -549,6 +616,23 @@ export interface OpWaveJob {
549
616
  command: string[];
550
617
  /** On a share job: the decision record to download. On a deciding job: the one to upload. */
551
618
  record?: string;
619
+ /**
620
+ * The outcome file the job writes (#4322), which the forge keeps whatever
621
+ * the job's exit code: each of its runs' outcome, as `../wave-blocked.ts`
622
+ * writes them. A job with `needs` reads the earlier ones from
623
+ * {@link opWaveOutcomeDir} first.
624
+ */
625
+ outcome: string;
626
+ }
627
+
628
+ /** Where a wave job writes its outcome file and finds the earlier ones (#4322), at any depth. */
629
+ export function opWaveOutcomeDir(spec: Pick<OpWavesSpec, "name">): string {
630
+ return `.chant/op-waves/${spec.name}/outcomes`;
631
+ }
632
+
633
+ /** The outcome file of wave `wave`'s job, or of one group or share of it (#4322). */
634
+ export function opWaveOutcomePath(spec: Pick<OpWavesSpec, "name">, wave: number, part: { group?: string; share?: number } = {}): string {
635
+ return `${opWaveOutcomeDir(spec)}/${waveOutcomeFileName(wave, part)}`;
552
636
  }
553
637
 
554
638
  const NAME = /^[a-z0-9][a-z0-9_-]*$/;
@@ -592,7 +676,7 @@ export function assertOpWavesSpec(spec: OpWavesSpec): void {
592
676
  }
593
677
  if (spec.approvals !== undefined) {
594
678
  const a = spec.approvals as unknown;
595
- if (a === null || typeof a !== "object" || Array.isArray(a)) throw new Error(`${where}: \`approvals\` must be { "spend"?: true | false, "quorum"?: [...] }.`);
679
+ if (a === null || typeof a !== "object" || Array.isArray(a)) throw new Error(`${where}: \`approvals\` must be { "spend"?: true | false, "quorum"?: [...], "reviewLedger"?: "<op>", "involved"?: {...} }.`);
596
680
  for (const [key, value] of Object.entries(a as Record<string, unknown>)) {
597
681
  if (key === "quorum") {
598
682
  // #4290: approval rules, read at base like the policy.
@@ -601,7 +685,17 @@ export function assertOpWavesSpec(spec: OpWavesSpec): void {
601
685
  if (problems.length > 0) throw new Error(`${where}: ${problems.join("; ")}.`);
602
686
  continue;
603
687
  }
604
- if (key !== "spend") throw new Error(`${where}: approvals.${key} is not a setting; the ones there are approvals.spend and approvals.quorum.`);
688
+ if (key === "reviewLedger") {
689
+ // #4333: the op a product records its reviews under, read at base.
690
+ if (typeof value !== "string" || !OP_LEDGER_NAME.test(value)) throw new Error(`${where}: approvals.reviewLedger must name an op, such as "tf-apply": letters, digits, dot, dash and underscore.`);
691
+ continue;
692
+ }
693
+ if (key === "involved") {
694
+ const problems = opWavesInvolvedProblems(value);
695
+ if (problems.length > 0) throw new Error(`${where}: ${problems.join("; ")}.`);
696
+ continue;
697
+ }
698
+ if (key !== "spend") throw new Error(`${where}: approvals.${key} is not a setting; the ones there are approvals.spend, approvals.quorum, approvals.reviewLedger and approvals.involved.`);
605
699
  if (typeof value !== "boolean") throw new Error(`${where}: approvals.spend must be true or false.`);
606
700
  }
607
701
  if ((a as OpWavesApprovals).quorum?.length && (spec.point !== undefined || spec.waves.some((w) => w.point !== undefined))) {
@@ -698,6 +792,41 @@ export function assertOpWavesSpec(spec: OpWavesSpec): void {
698
792
  if (problem) throw new Error(`${where}: ${problem}.`);
699
793
  }
700
794
  }
795
+ assertOpWaveDependsOn(where, spec);
796
+ }
797
+
798
+ /**
799
+ * Refuse a `dependsOn` (#4322) that names no run this one could follow: each
800
+ * entry is a target of an earlier wave, or of a run listed before it in its
801
+ * own wave when one job applies that wave. A discovering wave's runs are not
802
+ * in the spec, so after one any target is taken.
803
+ */
804
+ function assertOpWaveDependsOn(where: string, spec: OpWavesSpec): void {
805
+ const earlier = new Set<string>();
806
+ let discovered = false;
807
+ for (const wave of spec.waves) {
808
+ const own = new Set<string>();
809
+ for (const run of wave.runs ?? []) {
810
+ const deps = run.dependsOn as unknown;
811
+ if (deps !== undefined) {
812
+ const at = `wave "${wave.name}" run "${run.target}"`;
813
+ if (!Array.isArray(deps) || !deps.every((d) => typeof d === "string" && d !== "")) throw new Error(`${where}: ${at} dependsOn must be a list of targets.`);
814
+ for (const dep of deps as string[]) {
815
+ if (dep === run.target && !earlier.has(dep)) throw new Error(`${where}: ${at} depends on itself.`);
816
+ if (own.has(dep)) {
817
+ if ((wave.shares ?? 1) > 1 || wave.jobs !== undefined) {
818
+ throw new Error(`${where}: ${at} depends on "${dep}" in its own wave, which ${wave.jobs ? "job groups" : "shares"} apply in separate jobs; move one of them to another wave.`);
819
+ }
820
+ continue;
821
+ }
822
+ if (!earlier.has(dep) && !discovered) throw new Error(`${where}: ${at} depends on "${dep}", which is no run of an earlier wave or listed before it in its own.`);
823
+ }
824
+ }
825
+ own.add(run.target);
826
+ }
827
+ for (const t of own) earlier.add(t);
828
+ if (wave.discover) discovered = true;
829
+ }
701
830
  }
702
831
 
703
832
  const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -888,18 +1017,18 @@ export function assertOpWaveHooks(where: string, hooks: OpWaveHooks | undefined)
888
1017
  /**
889
1018
  * The moments a pull-request job runs hooks at (#4288): `pr-plan` and
890
1019
  * `pr-record` run the observe and plan moments, and `pr-apply` the apply
891
- * moments too. The pull request's gate has no `after-gate` moment.
1020
+ * moments too. `after-gate` (#4323) runs in `pr-apply` once the pull
1021
+ * request's gate has decided.
892
1022
  */
893
- export const PR_LOOP_HOOK_MOMENTS: readonly OpWaveHookMoment[] = ["before-observe", "before-plan", "after-plan", "before-apply", "after-apply"];
1023
+ export const PR_LOOP_HOOK_MOMENTS: readonly OpWaveHookMoment[] = ["before-observe", "before-plan", "after-plan", "after-gate", "before-apply", "after-apply"];
894
1024
 
895
1025
  /**
896
1026
  * Refuse `ci.hooks` a pull-request job could not run (#4288): what
897
- * {@link assertOpWaveHooks} refuses, plus `after.gate` and `apply.runBy`,
898
- * since chant itself runs a pull request's apply.
1027
+ * {@link assertOpWaveHooks} refuses, plus `apply.runBy`, since chant itself
1028
+ * runs a pull request's apply.
899
1029
  */
900
1030
  export function assertPrLoopHooks(where: string, hooks: OpWaveHooks | undefined): void {
901
1031
  assertOpWaveHooks(where, hooks);
902
- if (hooks?.after?.gate !== undefined) throw new Error(`${where}: hooks.after.gate runs in an Op waves job only; a pull-request job runs hooks before the observe, before and after the plan, and before and after the apply.`);
903
1032
  if (hooks?.apply !== undefined) throw new Error(`${where}: hooks.apply.runBy is for an Op waves spec; chant runs a pull request's apply, so it runs the apply-stage hooks itself.`);
904
1033
  }
905
1034
 
@@ -941,13 +1070,13 @@ export function opWaveJobs(spec: OpWavesSpec, specFile: string): OpWaveJob[] {
941
1070
  const prefix = `wave-${k}-${wave.name}`;
942
1071
  const shares = wave.shares ?? 1;
943
1072
  if (shares === 1 && wave.jobs === undefined) {
944
- jobs.push({ jobName: prefix, wave: k, waveName: wave.name, kind: "wave", needs: previous, command });
1073
+ jobs.push({ jobName: prefix, wave: k, waveName: wave.name, kind: "wave", needs: previous, command, outcome: opWaveOutcomePath(spec, k) });
945
1074
  previous = [prefix];
946
1075
  return;
947
1076
  }
948
1077
  const record = opWaveRecordPath(spec, k);
949
1078
  const decide = `${prefix}-decide`;
950
- jobs.push({ jobName: decide, wave: k, waveName: wave.name, kind: "decide", needs: previous, command: [...command, "--decide"], record });
1079
+ jobs.push({ jobName: decide, wave: k, waveName: wave.name, kind: "decide", needs: previous, command: [...command, "--decide"], record, outcome: opWaveOutcomePath(spec, k) });
951
1080
  const shareJobs: string[] = [];
952
1081
  if (wave.jobs !== undefined) {
953
1082
  // #3914: a job per group, or per share of a group, each applying its runs under the deciding job's decision.
@@ -957,13 +1086,13 @@ export function opWaveJobs(spec: OpWavesSpec, specFile: string): OpWaveJob[] {
957
1086
  if (groupShares === 1) {
958
1087
  const jobName = `${prefix}-${group.name}`;
959
1088
  shareJobs.push(jobName);
960
- jobs.push({ jobName, wave: k, waveName: wave.name, kind: "share", group: group.name, needs: [decide], command: groupCommand, record });
1089
+ jobs.push({ jobName, wave: k, waveName: wave.name, kind: "share", group: group.name, needs: [decide], command: groupCommand, record, outcome: opWaveOutcomePath(spec, k, { group: group.name }) });
961
1090
  continue;
962
1091
  }
963
1092
  for (let share = 1; share <= groupShares; share++) {
964
1093
  const jobName = `${prefix}-${group.name}-share-${share}`;
965
1094
  shareJobs.push(jobName);
966
- jobs.push({ jobName, wave: k, waveName: wave.name, kind: "share", group: group.name, share, needs: [decide], command: [...groupCommand, "--share", String(share)], record });
1095
+ jobs.push({ jobName, wave: k, waveName: wave.name, kind: "share", group: group.name, share, needs: [decide], command: [...groupCommand, "--share", String(share)], record, outcome: opWaveOutcomePath(spec, k, { group: group.name, share }) });
967
1096
  }
968
1097
  }
969
1098
  previous = shareJobs;
@@ -972,7 +1101,7 @@ export function opWaveJobs(spec: OpWavesSpec, specFile: string): OpWaveJob[] {
972
1101
  for (let share = 1; share <= shares; share++) {
973
1102
  const jobName = `${prefix}-share-${share}`;
974
1103
  shareJobs.push(jobName);
975
- jobs.push({ jobName, wave: k, waveName: wave.name, kind: "share", share, needs: [decide], command: [...command, "--share", String(share)], record });
1104
+ jobs.push({ jobName, wave: k, waveName: wave.name, kind: "share", share, needs: [decide], command: [...command, "--share", String(share)], record, outcome: opWaveOutcomePath(spec, k, { share }) });
976
1105
  }
977
1106
  previous = shareJobs;
978
1107
  });
package/src/op/runtime.ts CHANGED
@@ -175,6 +175,14 @@ export interface OpRunRecord {
175
175
  * run id. Absent outside CI and on a record written before 0.124.28.
176
176
  */
177
177
  runOrigin?: RunOrigin;
178
+ /**
179
+ * The workspace run that started this one (#2554, #2524 D17): `chant
180
+ * workspace run`'s id, and the workspace gate it ran under when it named
181
+ * one. The workspace run's own record, under `_members/_workspace/` on
182
+ * `chant/lifecycle`, names this run in turn. Absent on a run nothing in a
183
+ * workspace started, and on a record written before 0.124.47.
184
+ */
185
+ workspaceRun?: { id: string; gate?: string };
178
186
  /**
179
187
  * The run's statement (#4044): a DSSE envelope over an in-toto statement
180
188
  * (`https://intentius.io/chant/op-run/v1`) a runner key signed at the end
@@ -159,3 +159,36 @@ export async function checkOverdueOps(input: {
159
159
  }
160
160
  return out;
161
161
  }
162
+
163
+ /**
164
+ * The overdue check over the Ops declared at `cwd` (#4289), as the plan note
165
+ * reports it: the Ops `discoverOps` finds there, and the repository root
166
+ * `cwd` is in. `pr-post` runs it in its own checkout, the target branch's
167
+ * commit, and `chant ci run plan` in a checkout of `--base` (#4328), so the
168
+ * Ops are never read from the change's code. Without a forge that can say
169
+ * when a job ran, nothing is checked. A failure is a line in `log` and no
170
+ * verdict, and the plan goes on.
171
+ */
172
+ export async function overdueOpsAt(input: { forge: PrForge | undefined; cwd: string; member?: string; now?: Date; log: (line: string) => void }): Promise<OverdueOp[]> {
173
+ const { forge } = input;
174
+ if (!forge?.scheduledRuns) return [];
175
+ try {
176
+ const { discoverOps } = await import("./discover");
177
+ const discovered = await discoverOps({ cwd: input.cwd });
178
+ const watches = scheduledWatches([...discovered.ops.values()].map((d) => d.config));
179
+ if (watches.length === 0) return [];
180
+ const top = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd: input.cwd, encoding: "utf-8" });
181
+ if (top.status !== 0) return [];
182
+ return await checkOverdueOps({
183
+ watches,
184
+ forge,
185
+ repoRoot: top.stdout.trim(),
186
+ ...(input.member ? { member: input.member } : {}),
187
+ now: input.now ?? new Date(),
188
+ log: input.log,
189
+ });
190
+ } catch (err) {
191
+ input.log(`could not check the scheduled Ops, so none is reported overdue: ${err instanceof Error ? err.message : String(err)}`);
192
+ return [];
193
+ }
194
+ }
@@ -290,6 +290,11 @@ export interface RunStageHooksOptions {
290
290
  runsFile: string;
291
291
  /** `CHANT_HOLD`, relative to `cwd`. */
292
292
  holdFile: string;
293
+ /**
294
+ * Give the hooks that held on another gate in {@link StageHooksRun.otherGates}
295
+ * and leave them out of `holds` (#4323), for a caller that settles them.
296
+ */
297
+ keepOtherGates?: boolean;
293
298
  /** Added to every hook's environment. */
294
299
  env?: Record<string, string>;
295
300
  cwd?: string;
@@ -304,6 +309,8 @@ export interface StageHooksRun {
304
309
  failed?: string;
305
310
  /** The `onFailure: approve` hooks that failed. A hold on another gate counts as one here. */
306
311
  holds: string[];
312
+ /** The hook calls that held on another gate, with `keepOtherGates` (#4323); absent otherwise. */
313
+ otherGates?: OtherGateHold[];
307
314
  }
308
315
 
309
316
  /** {@link RunStageHooksOptions} for {@link runStageHooksAsync} (#4334). */
@@ -318,7 +325,8 @@ function stagePlace(options: Omit<RunStageHooksOptions, "exec">): Omit<HookPlace
318
325
  }
319
326
 
320
327
  /** A stage run's result: a hold on another gate counts as a plain hold. */
321
- function stageRun(out: HookListOutcome, name: string): StageHooksRun {
328
+ function stageRun(out: HookListOutcome, name: string, keep = false): StageHooksRun {
329
+ if (keep) return { outcomes: out.outcomes, ...(out.failed ? { failed: out.failed } : {}), holds: out.holds, otherGates: out.otherGates };
322
330
  return { outcomes: out.outcomes, ...(out.failed ? { failed: out.failed } : {}), holds: [...out.holds, ...out.otherGates.map((g) => `${g.label} (on ${g.request.op ?? name} / ${g.request.gate})`)] };
323
331
  }
324
332
 
@@ -326,12 +334,12 @@ function stageRun(out: HookListOutcome, name: string): StageHooksRun {
326
334
  * Run one moment's hooks outside an Op waves job (#4288): the pull-request
327
335
  * jobs, or a product's own stage, with the same `targets`, `perRun`,
328
336
  * `onFailure` and argv filling a wave job uses. A hook that holds on another
329
- * gate (`CHANT_HOLD`) is counted as a plain hold; settling that gate is the
330
- * caller's.
337
+ * gate (`CHANT_HOLD`) is counted as a plain hold, unless `keepOtherGates` is
338
+ * set (#4323): then it comes back in `otherGates` for the caller to settle.
331
339
  */
332
340
  export function runStageHooks(moment: OpWaveHookMoment, hooks: readonly OpWaveHook[] | undefined, options: RunStageHooksOptions): StageHooksRun {
333
341
  const place = { ...stagePlace(options), exec: options.exec ?? spawnExec };
334
- return stageRun(runHookList(place, moment, hooks, options.runs, options.runsFile, options.env), options.name);
342
+ return stageRun(runHookList(place, moment, hooks, options.runs, options.runsFile, options.env), options.name, options.keepOtherGates);
335
343
  }
336
344
 
337
345
  /**
@@ -342,5 +350,5 @@ export function runStageHooks(moment: OpWaveHookMoment, hooks: readonly OpWaveHo
342
350
  */
343
351
  export async function runStageHooksAsync(moment: OpWaveHookMoment, hooks: readonly OpWaveHook[] | undefined, options: RunStageHooksAsyncOptions): Promise<StageHooksRun> {
344
352
  const place = { ...stagePlace(options), exec: options.exec ?? spawnExecAsync };
345
- return stageRun(await runHookListAsync(place, moment, hooks, options.runs, options.runsFile, options.env), options.name);
353
+ return stageRun(await runHookListAsync(place, moment, hooks, options.runs, options.runsFile, options.env), options.name, options.keepOtherGates);
346
354
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * How a member's `chant run` learns that `chant workspace run` started it
3
+ * (#2554, #2524 D17). The workspace run sets these in the environment of the
4
+ * member's chant; the member's run record cites the workspace run, and a
5
+ * member gate may then ask the named workspace gate to stand in for it.
6
+ *
7
+ * Kept apart from the workspace code so the executor reads two environment
8
+ * variables and nothing more when no workspace run started it.
9
+ */
10
+
11
+ /** The workspace run's id. */
12
+ export const WORKSPACE_RUN_ENV = "CHANT_WORKSPACE_RUN";
13
+ /** The workspace gate the run was started under, when one was named. */
14
+ export const WORKSPACE_GATE_ENV = "CHANT_WORKSPACE_GATE";
15
+
16
+ /** What a run started by a workspace run knows about it. */
17
+ export interface WorkspaceRunCitation {
18
+ id: string;
19
+ /** The workspace gate the run was started under (`chant workspace run --gate`), when one was named. */
20
+ gate?: string;
21
+ }
22
+
23
+ /** The workspace run this process runs under, or undefined. */
24
+ export function resolveWorkspaceRun(env: NodeJS.ProcessEnv = process.env): WorkspaceRunCitation | undefined {
25
+ const id = env[WORKSPACE_RUN_ENV]?.trim();
26
+ if (!id) return undefined;
27
+ const gate = env[WORKSPACE_GATE_ENV]?.trim();
28
+ return { id, ...(gate ? { gate } : {}) };
29
+ }
@@ -34,7 +34,7 @@ describe("the pr-forge bundle", () => {
34
34
  expect(inputs.filter((i) => pattern.test(i)), String(pattern)).toEqual([]);
35
35
  }
36
36
  expect(inputs.map((i) => i.replace(/^.*?packages\//, "packages/")).sort()).toEqual(["packages/core/src/pr-forge.ts"]);
37
- // About 50 KB today, after the pull request authors (#4000), pullRequestOpen for target locks (#4293), the four requires checks of an apply before merge (#4301) the approver filter (#4337) and the approved fields (#4287).
38
- expect(result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0)).toBeLessThan(52 * 1024);
37
+ // About 52 KB today, after the pull request authors (#4000), pullRequestOpen for target locks (#4293), the four requires checks of an apply before merge (#4301) the approver filter (#4337) the approved fields (#4287) and the plan job's GitLab note (#4328).
38
+ expect(result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0)).toBeLessThan(56 * 1024);
39
39
  });
40
40
  });
@@ -321,6 +321,53 @@ describe("gitlab", () => {
321
321
  expect(await forge.pullRequestOpen!(5)).toBe(true);
322
322
  expect(await forge.pullRequestOpen!(6)).toBe(false);
323
323
  });
324
+
325
+ test("the plan job of each open merge request's newest pipeline at its head, in this project (#4328)", async () => {
326
+ const OLD = "b".repeat(40);
327
+ const { fetch, calls } = fakeFetch({
328
+ "GET /api/v4/projects/42/merge_requests": [
329
+ { iid: 9, sha: SHA, project_id: 42 },
330
+ { iid: 5, sha: SHA, project_id: 42 },
331
+ { iid: 6, sha: OLD, project_id: 42 },
332
+ ],
333
+ // Newest at the head first is 301; 300 is an older pipeline of the same head; 299 is of an older head.
334
+ "GET /api/v4/projects/42/merge_requests/5/pipelines": [
335
+ { id: 299, sha: OLD, project_id: 42 },
336
+ { id: 301, sha: SHA, project_id: 42 },
337
+ { id: 300, sha: SHA, project_id: 42 },
338
+ ],
339
+ "GET /api/v4/projects/42/pipelines/301/jobs": [
340
+ { id: 11, name: "plan", status: "success", web_url: "https://gitlab.example.com/acme/estate/-/jobs/11" },
341
+ { id: 12, name: "network-plan", status: "running" },
342
+ ],
343
+ // A fork's merge request whose pipeline ran in the fork: its jobs are not this project's.
344
+ "GET /api/v4/projects/42/merge_requests/6/pipelines": [{ id: 400, sha: OLD, project_id: 77 }],
345
+ // A merge request with no pipeline yet.
346
+ "GET /api/v4/projects/42/merge_requests/9/pipelines": [],
347
+ });
348
+ const forge = gitlabForge({ ...opts, fetch });
349
+ expect(await forge.mergeRequestJobs!("main", "plan")).toEqual([{ pr: 5, head: SHA, id: 11, status: "success", url: "https://gitlab.example.com/acme/estate/-/jobs/11" }]);
350
+ expect(new URL(calls[0]!.url).searchParams.get("target_branch")).toBe("main");
351
+ expect(new URL(calls[0]!.url).searchParams.get("state")).toBe("opened");
352
+ expect(calls.some((c) => c.url.includes("/pipelines/400/"))).toBe(false);
353
+ });
354
+
355
+ test("one file of a job's artifacts, undefined when the job kept none there, and the newest status's link (#4328)", async () => {
356
+ const { fetch, calls } = fakeFetch({
357
+ "GET /api/v4/projects/42/jobs/11/artifacts/infra/network/.chant/pr/pr-plan.json": { stage: "plan" },
358
+ [`GET /api/v4/projects/42/repository/commits/${SHA}/statuses`]: [
359
+ { id: 1, name: "chant/plan", target_url: "https://gitlab.example.com/-/jobs/10" },
360
+ { id: 3, name: "chant/plan", target_url: "https://gitlab.example.com/-/jobs/11" },
361
+ { id: 4, name: "chant/plan/network", target_url: "https://gitlab.example.com/-/jobs/12" },
362
+ ],
363
+ });
364
+ const forge = gitlabForge({ ...opts, fetch });
365
+ expect(JSON.parse((await forge.jobArtifact!(11, "infra/network/.chant/pr/pr-plan.json"))!)).toEqual({ stage: "plan" });
366
+ expect(await forge.jobArtifact!(12, ".chant/pr/pr-plan.json")).toBeUndefined();
367
+ expect(calls[0]!.headers["PRIVATE-TOKEN"]).toBe("glpat");
368
+ expect(await forge.statusUrl!(SHA, "chant/plan")).toBe("https://gitlab.example.com/-/jobs/11");
369
+ expect(await forge.statusUrl!(SHA, "chant/plan/other")).toBeUndefined();
370
+ });
324
371
  });
325
372
 
326
373
  describe("naming reviewers", () => {
package/src/pr-forge.ts CHANGED
@@ -223,6 +223,20 @@ export interface PullRequest {
223
223
  }
224
224
 
225
225
  /** An open pull request as `chant ci serve` polls it (#4095). */
226
+ /** A merge request's job in its newest pipeline at its head (#4328), as {@link PrForge.mergeRequestJobs} finds it. */
227
+ export interface MergeRequestJob {
228
+ /** The merge request's iid. */
229
+ pr: number;
230
+ /** The merge request's head commit, as GitLab says it. */
231
+ head: string;
232
+ /** The job's id, which {@link PrForge.jobArtifact} reads its artifacts by. */
233
+ id: number;
234
+ /** GitLab's word for the job: `success`, `failed` and `canceled` are settled, any other is not. */
235
+ status: string;
236
+ /** The job's page. */
237
+ url?: string;
238
+ }
239
+
226
240
  export interface OpenPullRequest {
227
241
  number: number;
228
242
  /** Its head branch. */
@@ -440,6 +454,17 @@ export interface PrForge {
440
454
  * Pending while one still runs or none has reported.
441
455
  */
442
456
  prChecksPassed?(pr: number, head: string, ignore?: readonly string[]): Promise<PrRequireResult>;
457
+ /**
458
+ * GitLab (#4328): each open merge request into `branch` whose newest
459
+ * pipeline at its head commit, in this project, ran a job named `job`,
460
+ * with that job. A merge request whose pipeline runs in a fork's project
461
+ * is left out, since its jobs and artifacts are the fork's. Oldest first.
462
+ */
463
+ mergeRequestJobs?(branch: string, job: string): Promise<MergeRequestJob[]>;
464
+ /** GitLab (#4328): the text of the file at `path` in job `id`'s artifacts, or undefined when it has none there. */
465
+ jobArtifact?(id: number, path: string): Promise<string | undefined>;
466
+ /** GitLab (#4328): the target URL of the newest commit status named `context` on `sha`, or undefined when there is none or it has no URL. */
467
+ statusUrl?(sha: string, context: string): Promise<string | undefined>;
443
468
  }
444
469
 
445
470
  /**
@@ -596,6 +621,8 @@ export function forgePrincipalOf(kind: ForgeKind, host: string, login: string):
596
621
 
597
622
  interface Transport {
598
623
  call(method: string, path: string, body?: unknown): Promise<unknown>;
624
+ /** A GET whose body is returned as text, not parsed; undefined on a 404. */
625
+ text(path: string): Promise<string | undefined>;
599
626
  /** Every page of a list, `pageParams(page)` appended to `path`. */
600
627
  list(path: string, pageParams: (page: number) => string, pageSize: number): Promise<unknown[]>;
601
628
  }
@@ -615,6 +642,14 @@ function transport(base: string, headers: Record<string, string>, fetchImpl: For
615
642
  };
616
643
  return {
617
644
  call,
645
+ async text(path) {
646
+ const url = `${root}${path}`;
647
+ const res = await fetchImpl(url, { method: "GET", headers });
648
+ const body = await res.text();
649
+ if (res.status === 404) return undefined;
650
+ if (!res.ok) throw new ForgeApiError("GET", url, res.status, body);
651
+ return body;
652
+ },
618
653
  async list(path, pageParams, pageSize) {
619
654
  const out: unknown[] = [];
620
655
  for (let page = 1; page <= 50; page++) {
@@ -1414,6 +1449,39 @@ export function gitlabForge(opts: GitlabForgeOptions): PrForge {
1414
1449
  const last = newestTime(pipelines.map((p) => (isObject(p) ? str(p.created_at) : undefined)));
1415
1450
  return { ...(last ? { last } : {}), ...(ours.length === 0 ? { noSchedule: true } : {}) };
1416
1451
  },
1452
+ async mergeRequestJobs(branch, job) {
1453
+ const out: MergeRequestJob[] = [];
1454
+ const open = await api.list(`${notes}?state=opened&target_branch=${encodeURIComponent(branch)}`, pageParams, 100);
1455
+ for (const m of open) {
1456
+ if (!isObject(m)) continue;
1457
+ const iid = num(m.iid);
1458
+ const head = str(m.sha);
1459
+ const own = num(m.project_id);
1460
+ if (iid === undefined || !head) continue;
1461
+ // Newest first; only this project's pipelines at the head count.
1462
+ const pipelines = (await api.list(`${notes}/${iid}/pipelines`, pageParams, 100))
1463
+ .filter((p): p is Record<string, unknown> => isObject(p) && p.sha === head && (own === undefined || num(p.project_id) === undefined || num(p.project_id) === own))
1464
+ .sort((a, b) => (num(b.id) ?? 0) - (num(a.id) ?? 0));
1465
+ const newest = pipelines[0];
1466
+ const pid = newest ? num(newest.id) : undefined;
1467
+ if (pid === undefined) continue;
1468
+ const jobs = await api.list(`${project}/pipelines/${pid}/jobs`, pageParams, 100);
1469
+ const found = jobs.filter((j): j is Record<string, unknown> => isObject(j) && j.name === job).sort((a, b) => (num(b.id) ?? 0) - (num(a.id) ?? 0))[0];
1470
+ const id = found ? num(found.id) : undefined;
1471
+ if (!found || id === undefined) continue;
1472
+ const url = str(found.web_url);
1473
+ out.push({ pr: iid, head, id, status: str(found.status) ?? "unknown", ...(url ? { url } : {}) });
1474
+ }
1475
+ return out.sort((a, b) => a.pr - b.pr);
1476
+ },
1477
+ async jobArtifact(id, path) {
1478
+ return api.text(`${project}/jobs/${id}/artifacts/${path.split("/").map(encodeURIComponent).join("/")}`);
1479
+ },
1480
+ async statusUrl(sha, context) {
1481
+ const statuses = await api.list(`${project}/repository/commits/${sha}/statuses?all=true&name=${encodeURIComponent(context)}`, pageParams, 100);
1482
+ const newest = statuses.filter((st): st is Record<string, unknown> => isObject(st) && st.name === context).sort((a, b) => (num(b.id) ?? 0) - (num(a.id) ?? 0))[0];
1483
+ return newest ? (str(newest.target_url) ?? undefined) : undefined;
1484
+ },
1417
1485
  async editPullRequest(mr, change) {
1418
1486
  return mergeRequest(await api.call("PUT", `${notes}/${mr}`, { ...(change.title !== undefined ? { title: change.title } : {}), ...(change.body !== undefined ? { description: change.body } : {}) }));
1419
1487
  },
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `chant components pr-post --open` refusals (#4328), through the CLI handler.
3
+ * Importing the handler loads the CLI's lexicon machinery, which runs past the
4
+ * unit-test budget (#2817), so this lives in the e2e project.
5
+ */
6
+
7
+ import { describe, expect, test, vi } from "vitest";
8
+ import type { CommandContext } from "./cli/registry";
9
+
10
+ describe("chant components pr-post --open refuses what it cannot post for (#4328)", () => {
11
+ const run = async (args: Record<string, unknown>): Promise<number> => {
12
+ const { runComponentsPrPost } = await import("./cli/handlers/pr");
13
+ return runComponentsPrPost({ args } as unknown as CommandContext);
14
+ };
15
+
16
+ test("--pr or --head, a forge other than GitLab, and no branch are each refused", async () => {
17
+ const errors: string[] = [];
18
+ const spy = vi.spyOn(console, "error").mockImplementation((line: unknown) => void errors.push(String(line)));
19
+ try {
20
+ expect(await run({ open: true, pr: 7, env: "prod", forge: "gitlab" })).toBe(1);
21
+ expect(await run({ open: true, env: "prod", forge: "github" })).toBe(1);
22
+ const branch = process.env.CI_COMMIT_BRANCH;
23
+ delete process.env.CI_COMMIT_BRANCH;
24
+ try {
25
+ expect(await run({ open: true, env: "prod", forge: "gitlab" })).toBe(1);
26
+ } finally {
27
+ if (branch !== undefined) process.env.CI_COMMIT_BRANCH = branch;
28
+ }
29
+ } finally {
30
+ spy.mockRestore();
31
+ }
32
+ expect(errors.join("\n")).toMatch(/takes no --pr or --head/);
33
+ expect(errors.join("\n")).toMatch(/--forge gitlab/);
34
+ expect(errors.join("\n")).toMatch(/pass --branch <branch>/);
35
+ });
36
+ });