@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
@@ -1,4 +1,4 @@
1
- import { migrateCommand, printMigrateResult } from "../commands/migrate";
1
+ import { lexiconOfSide, migrateCommand, printMigrateResult } from "../commands/migrate";
2
2
  import { formatError } from "../format";
3
3
  import { loadPlugins } from "../plugins";
4
4
  import type { CommandContext } from "../registry";
@@ -16,30 +16,39 @@ export async function runMigrate(ctx: CommandContext): Promise<number> {
16
16
  return 1;
17
17
  }
18
18
 
19
+ // A side is a lexicon, or a lexicon and a dialect: `sql/clickhouse` (#4399).
20
+ const toName = args.migrateTo ?? "gitlab";
21
+ const fromName = args.migrateFrom ?? "github";
22
+
23
+ // The sql pair writes declarations, so ts is its default; yaml is the gitlab pair's.
24
+ const emit = (args.emit as "yaml" | "ts" | "sql" | undefined) ?? (lexiconOfSide(toName) === "sql" ? "ts" : "yaml");
25
+ if (emit !== "yaml" && emit !== "ts" && emit !== "sql") {
26
+ console.error(formatError({ message: `Invalid --emit value: ${emit}. Expected 'yaml', 'ts' or 'sql'.` }));
27
+ return 1;
28
+ }
29
+
30
+ if (emit === "sql" && lexiconOfSide(toName) !== "sql") {
31
+ console.error(formatError({ message: `--emit sql writes DDL, and ${toName} is not a sql target`, hint: "use --emit yaml or --emit ts" }));
32
+ return 1;
33
+ }
34
+
19
35
  // migrate does not require a chant project context. Load the target
20
36
  // lexicon directly by name.
21
- const toName = args.migrateTo ?? "gitlab";
22
37
  let plugins;
23
38
  try {
24
- plugins = await loadPlugins([toName]);
39
+ plugins = await loadPlugins([lexiconOfSide(toName)]);
25
40
  } catch (err) {
26
41
  console.error(formatError({
27
42
  message: `Cannot load target lexicon "${toName}": ${err instanceof Error ? err.message : String(err)}`,
28
- hint: `Install @intentius/chant-lexicon-${toName} or pass --to <other-lexicon>`,
43
+ hint: `Install @intentius/chant-lexicon-${lexiconOfSide(toName)} or pass --to <other-lexicon>`,
29
44
  }));
30
45
  return 1;
31
46
  }
32
47
 
33
- const emit = (args.emit as "yaml" | "ts" | undefined) ?? "yaml";
34
- if (emit !== "yaml" && emit !== "ts") {
35
- console.error(formatError({ message: `Invalid --emit value: ${emit}. Expected 'yaml' or 'ts'.` }));
36
- return 1;
37
- }
38
-
39
48
  const result = await migrateCommand({
40
49
  sourceFile: args.path,
41
- from: args.migrateFrom ?? "github",
42
- to: args.migrateTo ?? "gitlab",
50
+ from: fromName,
51
+ to: toName,
43
52
  emit,
44
53
  strict: args.strict ?? false,
45
54
  validate: args.validate ?? false,
@@ -384,10 +384,19 @@ export async function runImport(ctx: CommandContext): Promise<number> {
384
384
  force: ctx.args.force,
385
385
  lexicon: ctx.args.lexicon,
386
386
  parserOptions: ctx.args.parserOption,
387
+ validate: ctx.args.validate,
387
388
  });
388
389
 
389
390
  printImportResult(result);
390
- return result.success ? 0 : 1;
391
+ if (!result.success) return 1;
392
+ // The review report and `--strict` (#4398): the parser's diagnostics, as `chant migrate` reports its own.
393
+ if (ctx.args.reportFile) {
394
+ const { writeImportReport } = await import("../commands/import-report");
395
+ const failed = writeImportReport(ctx.args.reportFile, resolve(ctx.args.path), result.diagnostics ?? [], CHANT_VERSION);
396
+ if (failed) console.error(formatWarning({ message: failed }));
397
+ }
398
+ const { importExitCode } = await import("../commands/import-report");
399
+ return importExitCode(result.diagnostics, ctx.args.strict === true);
391
400
  }
392
401
 
393
402
  export async function runUpdate(ctx: CommandContext): Promise<number> {
@@ -241,10 +241,10 @@ function componentsPrEnvAdapter(p: Prepared, run: { planAll: typeof import("./pr
241
241
  },
242
242
  async apply(target) {
243
243
  const planned = set!;
244
- const hookStop = run.runStageHook(p, "before-apply", p.plan.order, planned);
244
+ const hookStop = await run.runStageHook(p, "before-apply", p.plan.order, planned);
245
245
  if (hookStop) return { status: "stopped", rows: rows(target, () => "planned"), message: hookStop };
246
246
  const applied = await applyPrSet({ components: p.components, plan: p.plan, registry: p.registry, env: target.env, set: planned, gate: PR_ENV_GATE });
247
- const afterHook = run.runStageHook(p, "after-apply", p.plan.order, planned, "the environment stands as applied");
247
+ const afterHook = await run.runStageHook(p, "after-apply", p.plan.order, planned, "the environment stands as applied");
248
248
  const status = (component: string) => applied.members.find((m) => m.component === component)?.status ?? "planned";
249
249
  return {
250
250
  status: applied.status === "applied" ? "applied" : applied.status === "waiting" ? "waiting" : "failed",
@@ -65,8 +65,8 @@
65
65
  */
66
66
 
67
67
  import { spawnSync } from "node:child_process";
68
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
69
- import { join, resolve } from "node:path";
68
+ import { mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
69
+ import { join, relative, resolve, sep } from "node:path";
70
70
  import { loadChantConfig, resolveAutoReleaseDisabled, type ChantConfig } from "../../config";
71
71
  import { resolveCommit, resolveMergeBase } from "../../components/changed-files";
72
72
  import { deriveFanOut, fanOutRegistry } from "../../components/fan-out-support";
@@ -90,7 +90,8 @@ import { blockedMembers, blockedWords, settleEarlierWaves, type WaveMemberOutcom
90
90
  import { readFanOutAttempt, writeFanOutAttempt, type FanOutAttempt, type PrApplyRecord } from "../../components/fan-out-record";
91
91
  import { componentInputDigests, progressThatCarries } from "../../components/fan-out-inputs";
92
92
  import { samePlanDigest } from "../../lifecycle/plan-digest";
93
- import { gateIsSealed, gitGateLedgerPort, type GateCheck } from "../../op/gate";
93
+ import { evaluateGate, gateIsSealed, gitGateLedgerPort, type GateCheck, type GateLedgerPort } from "../../op/gate";
94
+ import type { OtherGateHold } from "../../op/stage-hooks";
94
95
  import { emitGateStep, gateEventsOf, GATE_PHASE, type RunEventSink, type RunEventStream } from "../../run-event-stream";
95
96
  import type { RunEventBody, RunEventComponentSettle, RunEventSettleStatus } from "../../run-event";
96
97
  import { openCliRunEvents, refusesEventsWithJson } from "../run-events";
@@ -130,8 +131,8 @@ import type { ComponentChangeSignal, FanOutPlan } from "../../components/fan-out
130
131
  import { failedRunExitCode, type DriverComponent } from "../../components/driver";
131
132
  import type { CapabilityRegistry } from "../../components/capability";
132
133
  import type { ComponentEnvironmentDeclaration } from "../../components/component";
133
- import { prLoopHooksAt, runPrHooks, type PrHookPlace, type PrLoopHooks } from "../../components/pr-hooks";
134
- import { decidePrPolicyOverride, prLoopPolicyAt, prPolicyReport, runPrPolicy, spendPrPolicyOverride, type PrLoopPolicy } from "../../components/pr-policy";
134
+ import { prLoopHooksAt, runPrHooks, type PrHoldSettled, type PrHookPlace, type PrLoopHooks } from "../../components/pr-hooks";
135
+ import { decidePrPolicyOverride, policyReviewOf, prLoopPolicyAt, prPolicyReport, runPrPolicy, spendPrPolicyOverride, type PrLoopPolicy } from "../../components/pr-policy";
135
136
  import type { OpWaveHookMoment } from "../../op/op-waves";
136
137
  import type { PrHookOutcome } from "../../pr-loop";
137
138
 
@@ -320,9 +321,19 @@ export function placeHooks(ctx: CommandContext, p: Prepared, stage: PrHookPlace[
320
321
  * when no hook failed. A hold is logged: the pull request's gate already
321
322
  * waits for a person's approval of the digest.
322
323
  */
323
- export function runStageHook(p: Prepared, moment: OpWaveHookMoment, components: readonly string[], planned?: PrPlanSet, what = "nothing was applied"): string | undefined {
324
+ export async function runStageHook(
325
+ p: Prepared,
326
+ moment: OpWaveHookMoment,
327
+ components: readonly string[],
328
+ planned?: PrPlanSet,
329
+ what = "nothing was applied",
330
+ env?: Record<string, string>,
331
+ ): Promise<string | undefined> {
324
332
  if (!p.hookPlace || components.length === 0) return undefined;
325
- const ran = runPrHooks(p.hooks, moment, p.hookPlace, components, planned ? { members: planned.members, plans: planned.plans } : undefined);
333
+ const ran = await runPrHooks(p.hooks, moment, p.hookPlace, components, planned ? { members: planned.members, plans: planned.plans } : undefined, {
334
+ ...(env ? { env } : {}),
335
+ settle: (hold) => settleHeldGate(p, hold),
336
+ });
326
337
  p.hookOutcomes.push(...ran.outcomes);
327
338
  for (const hold of ran.holds) console.error(formatInfo(`${hold} held, and the pull request's gate asks for an approval of the digest either way`));
328
339
  if (!ran.failed) return undefined;
@@ -330,6 +341,46 @@ export function runStageHook(p: Prepared, moment: OpWaveHookMoment, components:
330
341
  return p.hookFailed;
331
342
  }
332
343
 
344
+ /**
345
+ * Settle a hook's hold on another gate (#4323), the way an Op waves job does:
346
+ * an approval of that gate and digest under its `identity.gates` rule read at
347
+ * base, else a pending fact for it. The plan stage writes nothing, so there
348
+ * it only reads.
349
+ */
350
+ async function settleHeldGate(p: Prepared, hold: OtherGateHold): Promise<PrHoldSettled> {
351
+ const real = gitGateLedgerPort();
352
+ const ledger = p.hookPlace?.stage === "plan" ? readOnlyLedger(real) : real;
353
+ const port: GateLedgerPort = ledger.approvalRule ? { ...ledger, approvalRule: (g: string) => ledger.approvalRule!(g, { base: p.base }) } : ledger;
354
+ const op = hold.request.op ?? p.hookPlace?.op ?? "";
355
+ const check = await evaluateGate(port, {
356
+ op,
357
+ gate: hold.request.gate,
358
+ planDigest: hold.request.digest,
359
+ description: hold.request.description ?? `${hold.label} holds the pull request on it`,
360
+ fields: { ...hold.request.pending, approvalMode: "ledger", heldBy: { hook: hold.name, moment: hold.moment, ...(hold.target ? { target: hold.target } : {}) } },
361
+ });
362
+ if (check.satisfied) return { status: "approved", approvedBy: check.resolution.resolvedBy, approvedAt: check.resolution.timestamp };
363
+ for (const { resolution: r, reason } of check.refused ?? []) console.error(formatInfo(`${op} / ${hold.request.gate}: the approval by ${r.resolvedBy} at ${r.timestamp} does not count: ${reason}`));
364
+ return { status: "waiting" };
365
+ }
366
+
367
+ /**
368
+ * Run the `after-gate` hooks once the pull request's gate decided (#4323),
369
+ * whatever it decided. `CHANT_GATE_STATUS` is `approved`, or `waiting` when no
370
+ * approval of the digest stands; `CHANT_GATE_OP`, `CHANT_GATE` and
371
+ * `CHANT_GATE_DIGEST` name the gate. Returns the failure message, as
372
+ * {@link runStageHook} does; the caller ignores it when the gate did not let
373
+ * the apply through, since the stage ends there anyway.
374
+ */
375
+ export function runGateHook(p: Prepared, at: { op: string; digest: string; satisfied: boolean }, components: readonly string[]): Promise<string | undefined> {
376
+ return runStageHook(p, "after-gate", components, undefined, "nothing was applied", {
377
+ CHANT_GATE_STATUS: at.satisfied ? "approved" : "waiting",
378
+ CHANT_GATE_OP: at.op,
379
+ CHANT_GATE: p.gate,
380
+ CHANT_GATE_DIGEST: at.digest,
381
+ });
382
+ }
383
+
333
384
  const EMPTY_SET = (): PrPlanSet => ({ doc: composeChangeSet([]), members: [], plans: {}, outputs: {}, failed: true });
334
385
 
335
386
  /**
@@ -338,11 +389,11 @@ const EMPTY_SET = (): PrPlanSet => ({ doc: composeChangeSet([]), members: [], pl
338
389
  * set, so the stage reports `plan-failed` and nothing applies.
339
390
  */
340
391
  async function hookedPlan(p: Prepared, components: readonly string[], plan: () => Promise<PrPlanSet>): Promise<PrPlanSet> {
341
- if (runStageHook(p, "before-observe", components, undefined, "nothing was planned or applied")) return EMPTY_SET();
342
- if (runStageHook(p, "before-plan", components, undefined, "nothing was planned or applied")) return EMPTY_SET();
392
+ if (await runStageHook(p, "before-observe", components, undefined, "nothing was planned or applied")) return EMPTY_SET();
393
+ if (await runStageHook(p, "before-plan", components, undefined, "nothing was planned or applied")) return EMPTY_SET();
343
394
  const set = await plan();
344
395
  if (set.failed) return set;
345
- if (runStageHook(p, "after-plan", components, set)) return { ...set, failed: true };
396
+ if (await runStageHook(p, "after-plan", components, set)) return { ...set, failed: true };
346
397
  return set;
347
398
  }
348
399
 
@@ -570,7 +621,9 @@ interface PolicyJudged {
570
621
  * read only at the plan stage, and recorded for an override at the apply.
571
622
  */
572
623
  async function judgePolicy(p: Prepared, at: { stage: "plan" | "apply"; op: string; pr: number | null; set: PrPlanSet }): Promise<PolicyJudged> {
573
- const verdict = runPrPolicy(p.policy, { version: 1, stage: at.stage, op: at.op, gate: p.gate, env: p.env, pr: at.pr, base: p.base, head: p.head, changeSet: at.set.doc });
624
+ // The review is read only when a policy will run: a workspace with no policy makes no forge call for it.
625
+ const review = p.policy.settings ? await policyReviewOf(p.forge, at.pr) : undefined;
626
+ const verdict = runPrPolicy(p.policy, { version: 1, stage: at.stage, op: at.op, gate: p.gate, env: p.env, pr: at.pr, base: p.base, head: p.head, changeSet: at.set.doc, ...(review ? { review } : {}) });
574
627
  if (!verdict) return { held: false };
575
628
  const report = prPolicyReport(verdict, p.policy.base);
576
629
  p.policyReport = report;
@@ -712,27 +765,8 @@ async function prPlan(ctx: CommandContext, stage: StageEvents): Promise<number>
712
765
  * `pr-post` asks instead, reading the Ops at the target branch's commit.
713
766
  */
714
767
  async function overdueOps(forge: PrForge | undefined, member: string | undefined): Promise<PrOverdue[]> {
715
- if (!forge?.scheduledRuns) return [];
716
- try {
717
- const { discoverOps } = await import("../../op/discover");
718
- const { scheduledWatches, checkOverdueOps } = await import("../../op/schedule-overdue");
719
- const discovered = await discoverOps({ cwd: resolve(".") });
720
- const watches = scheduledWatches([...discovered.ops.values()].map((d) => d.config));
721
- if (watches.length === 0) return [];
722
- const top = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" });
723
- if (top.status !== 0) return [];
724
- return await checkOverdueOps({
725
- watches,
726
- forge,
727
- repoRoot: top.stdout.trim(),
728
- ...(member ? { member } : {}),
729
- now: new Date(),
730
- log: (line) => console.error(formatWarning({ message: line })),
731
- });
732
- } catch (err) {
733
- console.error(formatWarning({ message: `could not check the scheduled Ops, so none is reported overdue: ${err instanceof Error ? err.message : String(err)}` }));
734
- return [];
735
- }
768
+ const { overdueOpsAt } = await import("../../op/schedule-overdue");
769
+ return overdueOpsAt({ forge, cwd: resolve("."), ...(member ? { member } : {}), log: (line) => console.error(formatWarning({ message: line })) });
736
770
  }
737
771
 
738
772
  /** chant components pr-apply --base <ref> [--pr <n>] [--env <env>] [--gate <name>] [--forge <kind>] [--require-review] [--output <dir>] [--json] [--events-json] [--report-url <url>] */
@@ -881,6 +915,9 @@ async function prApply(ctx: CommandContext, stage: StageEvents, beforeMerge?: Pr
881
915
  if (judged.stop) return stop(prReport({ ...base, approval: { status: "pending" }, status: "refused", refusal: judged.stop.refusal, message: judged.stop.message }), judged.stop.code);
882
916
  const decided = await decideApplyGate(p, { op, pr, set, events, held: judged.held, ...(green ? { green } : {}) });
883
917
  approval = decided.approval;
918
+ // #4323: the after-gate hooks hear the decision, a refusal too; only a failure that would have applied changes the outcome.
919
+ const afterGate = await runGateHook(p, { op, digest: set.doc.digest, satisfied: decided.satisfied }, p.plan.order);
920
+ if (afterGate && decided.satisfied) return stop(prReport({ ...base, approval, status: "failed", message: afterGate }), 1);
884
921
  if (!decided.satisfied) return stop(prReport({ ...base, approval, status: "refused", refusal: decided.refusal, message: decided.message }), GATED_EXIT_CODE);
885
922
  via = decided.via;
886
923
  }
@@ -935,7 +972,7 @@ async function prApply(ctx: CommandContext, stage: StageEvents, beforeMerge?: Pr
935
972
 
936
973
  // #4288: the before-apply hooks, once the gate and the review let the apply through.
937
974
  const toApply = (resumed?.plan ?? p.plan).order.filter((name) => !completedSoFar.has(name));
938
- const beforeFailed = runStageHook(p, "before-apply", toApply);
975
+ const beforeFailed = await runStageHook(p, "before-apply", toApply);
939
976
  if (beforeFailed) return stop(prReport({ ...base, approval, status: "failed", message: beforeFailed }), 1);
940
977
 
941
978
  // #3999: a round planned after an upstream applied waits on a gate of its
@@ -994,7 +1031,7 @@ async function prApply(ctx: CommandContext, stage: StageEvents, beforeMerge?: Pr
994
1031
  });
995
1032
  recordAttempt();
996
1033
  // #4288: the after-apply hooks, for the components that applied.
997
- const afterFailed = runStageHook(p, "after-apply", toApply.filter((name) => applied.run.completed.includes(name)), undefined, "the apply counts as failed though its components applied");
1034
+ const afterFailed = await runStageHook(p, "after-apply", toApply.filter((name) => applied.run.completed.includes(name)), undefined, "the apply counts as failed though its components applied");
998
1035
  await recordAutoReleasesForRun(applied.run.results, p.env, release.runId, resolveAutoReleaseDisabled(p.config, args.noReleaseRecord), release.runOrigin, undefined, undefined, prAppliedDigests(p, applied.members));
999
1036
  const report = prReport({
1000
1037
  ...base,
@@ -1787,6 +1824,9 @@ async function prApplyWaves(
1787
1824
  if (judged.stop) return stop(report({ status: "refused", refusal: judged.stop.refusal, message: judged.stop.message }), judged.stop.code);
1788
1825
  const decided = await decideApplyGate(p, { op, pr, set, events, held: judged.held, ...(green ? { green } : {}) });
1789
1826
  approval = decided.approval;
1827
+ // #4323: the after-gate hooks hear the decision, a refusal too.
1828
+ const afterGate = await runGateHook(p, { op, digest: set.doc.digest, satisfied: decided.satisfied }, p.plan.order);
1829
+ if (afterGate && decided.satisfied) return stop(report({ status: "failed", message: afterGate }), 1);
1790
1830
  if (!decided.satisfied) return stop(report({ status: "refused", refusal: decided.refusal, message: decided.message }), GATED_EXIT_CODE);
1791
1831
  via = decided.via;
1792
1832
  planned = set;
@@ -1944,7 +1984,7 @@ async function prApplyWaves(
1944
1984
  }
1945
1985
 
1946
1986
  // #4288: the wave's apply-side hooks, around its apply.
1947
- const waveBefore = runStageHook(p, "before-apply", wavePlan.order, undefined, `nothing in wave ${k} was applied`);
1987
+ const waveBefore = await runStageHook(p, "before-apply", wavePlan.order, undefined, `nothing in wave ${k} was applied`);
1948
1988
  if (waveBefore) {
1949
1989
  setWave({ ...entry, status: "failed" });
1950
1990
  return stop(report({ status: "failed", message: waveBefore }), 1);
@@ -1979,7 +2019,7 @@ async function prApplyWaves(
1979
2019
  const outputs = applied.run.componentOutputs[name];
1980
2020
  if (outputs) outputsSoFar[name] = outputs;
1981
2021
  }
1982
- const waveAfter = runStageHook(p, "after-apply", wavePlan.order.filter((name) => applied.run.completed.includes(name)), undefined, `wave ${k} counts as failed though its components applied`);
2022
+ const waveAfter = await runStageHook(p, "after-apply", wavePlan.order.filter((name) => applied.run.completed.includes(name)), undefined, `wave ${k} counts as failed though its components applied`);
1983
2023
  setWave({ ...entry, status: applied.status === "applied" && !waveAfter ? "applied" : "failed" });
1984
2024
  await recordAutoReleasesForRun(applied.run.results, p.env, at.release.runId, resolveAutoReleaseDisabled(p.config, args.noReleaseRecord), at.release.runOrigin, undefined, undefined, prAppliedDigests(p, applied.members));
1985
2025
  if (applied.status !== "applied") {
@@ -2023,6 +2063,7 @@ function writeWaveChangeSet(ctx: CommandContext, wave: number, doc: PrPlanSet["d
2023
2063
 
2024
2064
  /**
2025
2065
  * chant components pr-post --pr <n> --head <sha> --env <env> --forge <kind> [--member <name>] [--output <dir>] [--result <result>] (#4295)
2066
+ * chant components pr-post --open --env <env> --forge gitlab [--branch <branch>] [--member <name>] [--output <dir>] (#4328)
2026
2067
  *
2027
2068
  * Post the plan note and the `chant/plan` status from the report a plan job
2028
2069
  * wrote with `pr-plan --no-status`, in a job that runs none of the change's
@@ -2033,6 +2074,7 @@ function writeWaveChangeSet(ctx: CommandContext, wave: number, doc: PrPlanSet["d
2033
2074
  */
2034
2075
  export async function runComponentsPrPost(ctx: CommandContext): Promise<number> {
2035
2076
  const { args } = ctx;
2077
+ if (args.open) return runComponentsPrPostOpen(ctx);
2036
2078
  if (args.pr === undefined) return fail("pr-post needs the pull or merge request: --pr <n>");
2037
2079
  if (!args.head || !/^[0-9a-f]{40}$/.test(args.head)) return fail("pr-post needs the full commit the plan job checked out: --head <sha>");
2038
2080
  if (!args.env) return fail("pr-post needs the environment the plan was for: --env <env>");
@@ -2062,3 +2104,63 @@ export async function runComponentsPrPost(ctx: CommandContext): Promise<number>
2062
2104
  console.error(formatInfo(`pr-post: ${prStatusContext("plan", member)} ${posted.state}: ${posted.description}${posted.noted ? "; note posted" : ""}`));
2063
2105
  return 0;
2064
2106
  }
2107
+
2108
+ /**
2109
+ * `pr-post --open` (#4328): in a pipeline of the target branch on GitLab,
2110
+ * post the plan note and status of every open merge request into it whose
2111
+ * plan job settled, reading each report from the plan job's artifacts
2112
+ * (`postOpenPlanReports`, ../../pr-post.ts). The branch is `--branch`, else
2113
+ * the pipeline's own (`CI_COMMIT_BRANCH`). The plan job is `plan`, or
2114
+ * `<member>-plan` with `--member`, as the gitlab lexicon names it, and the
2115
+ * artifact path is `--output` from the repository root. It exits 0 once it
2116
+ * has posted, as `pr-post` does.
2117
+ */
2118
+ async function runComponentsPrPostOpen(ctx: CommandContext): Promise<number> {
2119
+ const { args } = ctx;
2120
+ if (args.pr !== undefined || args.head) return fail("pr-post --open finds each merge request and its head itself, so it takes no --pr or --head");
2121
+ if (!args.env) return fail("pr-post needs the environment the plan was for: --env <env>");
2122
+ if (args.forge !== "gitlab") return fail("pr-post --open is for a GitLab pipeline of the target branch: --forge gitlab");
2123
+ if (args.members && args.members.length > 1) return fail(`pr-post takes one --member, not ${args.members.join(", ")}`);
2124
+ const branch = args.branch ?? process.env.CI_COMMIT_BRANCH;
2125
+ if (!branch) return fail("pr-post --open posts for the merge requests into a branch: run it in a branch pipeline, or pass --branch <branch>");
2126
+ let forge: PrForge;
2127
+ try {
2128
+ forge = forgeFromEnv("gitlab");
2129
+ } catch (err) {
2130
+ return fail(err instanceof Error ? err.message : String(err));
2131
+ }
2132
+ const top = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" });
2133
+ if (top.status !== 0) return fail("pr-post --open reads the plan report's path from the repository root, and this directory is not in a git repository");
2134
+ const member = args.members?.[0];
2135
+ const artifactDir = relative(realpathSync(top.stdout.trim()), resolve(args.output ?? PR_REPORT_DIR)).split(sep).join("/");
2136
+ const { postOpenPlanReports } = await import("../../pr-post");
2137
+ const { gitlabPlanJobName } = await import("../../components/pr-pipeline");
2138
+ let done: Awaited<ReturnType<typeof postOpenPlanReports>>;
2139
+ try {
2140
+ done = await postOpenPlanReports({
2141
+ forge,
2142
+ branch,
2143
+ job: gitlabPlanJobName(member),
2144
+ artifactDir,
2145
+ env: args.env,
2146
+ ...(member ? { member } : {}),
2147
+ // #4289: the Ops at the commit this pipeline checked out, the target branch's.
2148
+ overdue: () => overdueOps(forge, member),
2149
+ });
2150
+ } catch (err) {
2151
+ return fail(err instanceof Error ? err.message : String(err));
2152
+ }
2153
+ for (const message of done.warnings) console.error(formatWarning({ message }));
2154
+ for (const p of done.posts) {
2155
+ if (p.skipped) {
2156
+ console.error(formatInfo(`pr-post: !${p.pr}: ${p.skipped}`));
2157
+ continue;
2158
+ }
2159
+ const posted = p.posted!;
2160
+ for (const message of posted.warnings) console.error(formatWarning({ message: `!${p.pr}: ${message}` }));
2161
+ if (posted.refused) console.error(formatWarning({ message: `!${p.pr}: the plan report was not used: ${posted.refused}` }));
2162
+ console.error(formatInfo(`pr-post: !${p.pr} ${prStatusContext("plan", member)} ${posted.state}: ${posted.description}${posted.noted ? "; note posted" : ""}`));
2163
+ }
2164
+ if (done.posts.length === 0) console.error(formatInfo(`pr-post: no open merge request into ${branch} has a ${gitlabPlanJobName(member)} job`));
2165
+ return 0;
2166
+ }
@@ -18,11 +18,23 @@ import { findWorkspaceRoot } from "../../project-root";
18
18
 
19
19
  const USAGE = 'chant query "<sql>" [dir] [--member <name>] [--json]';
20
20
 
21
- /** The ledgers `chant query` reads from `cwd`: every member's in a workspace, else the project's own. */
22
- export async function queryLedgers(cwd: string, members?: readonly string[]): Promise<QueryLedger[]> {
21
+ /**
22
+ * The ledgers `chant query` and `chant ledger` read from `cwd`: every
23
+ * member's in a workspace, else the project's own. With `at`, a commit of
24
+ * `chant/lifecycle` (`chant ledger --at`, #4363), each is read as that commit
25
+ * held it rather than at the branch tip.
26
+ */
27
+ export async function queryLedgers(cwd: string, members?: readonly string[], at?: string): Promise<QueryLedger[]> {
23
28
  const { gitLedgerReadPort } = await import("../../lifecycle/git");
24
29
  const found = findWorkspaceRoot(cwd);
25
- if (!found) return [{ member: null, port: gitLedgerReadPort({ cwd }) }];
30
+ const atPort = at === undefined ? undefined : await (async () => {
31
+ const { resolveLedgerRevision, revisionLedgerReadPort } = await import("../../lifecycle/ledger-read");
32
+ const { memberLedgerPrefix } = await import("../../lifecycle/member-ledger");
33
+ const repo = found?.dir ?? cwd;
34
+ const commit = resolveLedgerRevision(repo, at);
35
+ return (member: string | null) => revisionLedgerReadPort(repo, commit, member === null ? "" : memberLedgerPrefix([member]));
36
+ })();
37
+ if (!found) return [{ member: null, port: atPort ? atPort(null) : gitLedgerReadPort({ cwd }) }];
26
38
  // Workspace code loads only when there is a workspace, so a level-0 project never pays for it.
27
39
  const { readDeclaration, WorkspaceReadError } = await import("../../workspace/declaration");
28
40
  const { workingTree } = await import("../../workspace/tree");
@@ -41,7 +53,7 @@ export async function queryLedgers(cwd: string, members?: readonly string[]): Pr
41
53
  }
42
54
  return declaration.members
43
55
  .filter((m) => !wanted || wanted.has(m.name))
44
- .map((m) => ({ member: m.name, port: gitLedgerReadPort({ cwd: found.dir, member: m.dir === "." ? "." : m.name }) }));
56
+ .map((m) => ({ member: m.name, port: atPort ? atPort(m.dir === "." ? null : m.name) : gitLedgerReadPort({ cwd: found.dir, member: m.dir === "." ? "." : m.name }) }));
45
57
  }
46
58
 
47
59
  export async function runQueryCommand(ctx: CommandContext): Promise<number> {
@@ -174,9 +174,12 @@ export async function runReleaseCut(ctx: CommandContext): Promise<number> {
174
174
  const { scopeSource } = await import("../../workspace/write-scope");
175
175
  const { releaseRuleFrom } = await import("../../workspace/identity");
176
176
  const source = scopeSource(cwd);
177
- trunk = ws.resolveTrunk(top, source.declaration?.ci?.green?.branch ?? null);
177
+ const greenDecl = source.declaration?.ci?.green ?? null;
178
+ const primary = ws.resolveTrunk(top, greenDecl?.branch ?? null);
179
+ trunk = primary;
178
180
  try {
179
- ws.checkOnTrunk(top, commit, trunk);
181
+ // #4325: a commit of a branch ci.green also judges (one ci.branches routes members to) is cut like trunk's.
182
+ trunk = ws.checkOnGreenBranches(top, commit, primary, (greenDecl?.branches ?? []).slice(1));
180
183
  } catch (err) {
181
184
  // #4168 4.3: a commit of a pushed compose branch is cut too, and the cut records what it carries.
182
185
  if (!(err instanceof ws.WorkstationCheckError) || err.code !== "release-commit-not-on-trunk") throw err;
@@ -552,7 +555,7 @@ export async function runReleaseVerify(ctx: CommandContext): Promise<number> {
552
555
  */
553
556
  export async function runReleaseCandidates(ctx: CommandContext): Promise<number> {
554
557
  const { releaseCandidatesDocument } = await import("../../workspace/release-candidates");
555
- const doc = await releaseCandidatesDocument({ cwd: resolve("."), ...(ctx.args.env ? { env: ctx.args.env } : {}) });
558
+ const doc = await releaseCandidatesDocument({ cwd: resolve("."), ...(ctx.args.env ? { env: ctx.args.env } : {}), ...(ctx.args.branch ? { branch: ctx.args.branch } : {}) });
556
559
  if (ctx.args.json) {
557
560
  console.log(JSON.stringify(doc, null, 2));
558
561
  return "error" in doc ? 1 : 0;
@@ -22,6 +22,7 @@ import { collectBuildRootContributors, loadPlugins, resolveProjectLexicons } fro
22
22
  import { declaredBuildRootContributors, mergeDeclaredBuildRoots, withLexiconEdges } from "./declared-graph";
23
23
  import { parseCountBy, countGroups, countsJson, formatCounts, groupMembers, hasManyTerraformRoots, type CountBy } from "./search-count";
24
24
  import { formatError, formatWarning } from "../format";
25
+ import { adjacencyOf, walkBreadthFirst } from "../../graph-walk";
25
26
  import type { CommandContext } from "../registry";
26
27
  import { SEARCH_CONTRACT_VERSION, SEARCH_OUTPUT_SCHEMA_ID, type SearchErrorCode, type SearchFailure, type SearchHead } from "../../workspace/search-contract";
27
28
 
@@ -1870,13 +1871,7 @@ const adjacency = new WeakMap<GraphIR, { out: Map<string, IREdge[]>; in: Map<str
1870
1871
  function edgeIndex(ir: GraphIR): { out: Map<string, IREdge[]>; in: Map<string, IREdge[]> } {
1871
1872
  let idx = adjacency.get(ir);
1872
1873
  if (!idx) {
1873
- idx = { out: new Map(), in: new Map() };
1874
- for (const e of ir.edges ?? []) {
1875
- if (!idx.out.has(e.from)) idx.out.set(e.from, []);
1876
- idx.out.get(e.from)!.push(e);
1877
- if (!idx.in.has(e.to)) idx.in.set(e.to, []);
1878
- idx.in.get(e.to)!.push(e);
1879
- }
1874
+ idx = adjacencyOf(ir.edges ?? []);
1880
1875
  adjacency.set(ir, idx);
1881
1876
  }
1882
1877
  return idx;
@@ -1894,17 +1889,14 @@ function sameName(a: string | undefined, b: string): boolean {
1894
1889
  * the target leaf (any node when the term has none) at a depth inside the
1895
1890
  * term's range. A plain `->`/`<-` is the range 1..1.
1896
1891
  *
1897
- * Termination on a cycle: a state is (node, depth capped at the range's
1898
- * minimum). Past the minimum, arriving at a node again by a longer walk can
1899
- * reach nothing the first arrival could not, so it is not queued twice. The
1900
- * state space is finite and the walk always ends.
1892
+ * The walk is `walkBreadthFirst` (graph-walk.ts), shared with carve's
1893
+ * reachability (#2325); it is what terminates on a cycle.
1901
1894
  */
1902
1895
  function findPath(n: IRNode, t: Term, ir: GraphIR, byId: Map<string, IRNode>): PathHop[] | null {
1903
1896
  if (t.kind !== "edge") return null;
1904
1897
  const { min, max } = t.depth ?? { min: 1, max: 1 };
1905
1898
  const idx = edgeIndex(ir);
1906
1899
  const out = t.dir === "out";
1907
- type State = { id: string; depth: number; hops: PathHop[] };
1908
1900
  // A capability edge (#4375) stands for the hops that proved it.
1909
1901
  const steps = (id: string): { far: string; hops: PathHop[] }[] => {
1910
1902
  if (t.capability !== undefined) return capabilitySteps(id, t.capability, out, ir);
@@ -1915,28 +1907,15 @@ function findPath(n: IRNode, t: Term, ir: GraphIR, byId: Map<string, IRNode>): P
1915
1907
  hops: [{ from: e.from, ...(e.viaAttr ? { fromAttr: e.viaAttr } : {}), to: e.to, ...(e.toAttr ? { toAttr: e.toAttr } : {}) }],
1916
1908
  })));
1917
1909
  };
1918
- const seen = new Set<string>([`${n.id}|0`]);
1919
- let frontier: State[] = [{ id: n.id, depth: 0, hops: [] }];
1920
- while (frontier.length > 0) {
1921
- const next: State[] = [];
1922
- for (const s of frontier) {
1923
- if (s.depth >= max) continue;
1924
- for (const step of steps(s.id)) {
1925
- const farId = step.far;
1926
- const far = byId.get(farId);
1927
- if (!far) continue;
1928
- const depth = s.depth + 1;
1929
- const hops = [...s.hops, ...step.hops];
1930
- if (depth >= min && (!t.sub || matchTerm(far, t.sub, ir, byId))) return hops;
1931
- const key = `${farId}|${Math.min(depth, min)}`;
1932
- if (seen.has(key)) continue;
1933
- seen.add(key);
1934
- next.push({ id: farId, depth, hops });
1935
- }
1936
- }
1937
- frontier = next;
1938
- }
1939
- return null;
1910
+ // A node the graph does not hold is no step at all, and the match is the
1911
+ // first arrival in range that satisfies the target leaf (#4359).
1912
+ const found = walkBreadthFirst<PathHop>(n.id, {
1913
+ min,
1914
+ max,
1915
+ steps: (id) => steps(id).filter((step) => byId.has(step.far)),
1916
+ arrive: ({ id }) => !t.sub || matchTerm(byId.get(id)!, t.sub, ir, byId),
1917
+ });
1918
+ return found ? found.hops : null;
1940
1919
  }
1941
1920
 
1942
1921
  function bareId(id: string): string {
@@ -869,10 +869,12 @@ describe("workspace init and ls (#2534)", () => {
869
869
  "workspace pin",
870
870
  "workspace points",
871
871
  "workspace records",
872
+ "workspace run",
872
873
  "workspace runs",
873
874
  "workspace search",
874
875
  "workspace signers",
875
876
  "workspace status",
877
+ "workspace train",
876
878
  "workspace trust",
877
879
  "workspace upgrade",
878
880
  "workspace verify",
@@ -882,6 +884,19 @@ describe("workspace init and ls (#2534)", () => {
882
884
  ]);
883
885
  });
884
886
 
887
+ test("run and train take their targets and flags (#2554)", () => {
888
+ const run = parseArgs(["workspace", "run", "api/deploy", "web/deploy", "--gate", "release", "--env", "prod"]);
889
+ expect(run).toMatchObject({ command: "workspace", path: "run", positionals: ["run", "api/deploy", "web/deploy"], gate: "release", env: "prod" });
890
+ expect(resolveCommand(run, commandRegistry)?.def.name).toBe("workspace run");
891
+ expect(parseArgs(["workspace", "run", "--dependents-of", "network", "--op", "deploy"])).toMatchObject({ dependentsOf: "network", op: "deploy" });
892
+ expect(() => parseArgs(["workspace", "run", "--dependents-of"])).toThrow(/--dependents-of needs a member/);
893
+ const train = parseArgs(["workspace", "train", "prod", "--pin", "api=v2", "--pin", "web=v1"]);
894
+ expect(train).toMatchObject({ positionals: ["train", "prod"], pins: ["api=v2", "web=v1"] });
895
+ expect(resolveCommand(train, commandRegistry)?.def.name).toBe("workspace train");
896
+ expect(() => parseArgs(["workspace", "train", "prod", "--pin", "api"])).toThrow(/--pin needs a member and a release/);
897
+ expect(parseArgs(["workspace", "lint", "--cross-root", "--format", "json"])).toMatchObject({ path: "lint", crossRoot: true, format: "json" });
898
+ });
899
+
885
900
  test("adopt-lineage, hash-index, versions and upgrade --source take their flags (#2551)", () => {
886
901
  const adopt = parseArgs(["workspace", "adopt-lineage", "app", "--from", "acme/starter#svc", "--tags", "v*", "--index", "i.json", "--param", "name=x", "--dry-run"]);
887
902
  expect(adopt).toMatchObject({ command: "workspace", path: "adopt-lineage", extraPositional: "app", migrateFrom: "acme/starter#svc", tags: "v*", index: "i.json", param: ["name=x"], dryRun: true });