@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
@@ -53,7 +53,9 @@
53
53
  * report with its own token (`postPlanReport`, ../pr-post.ts). In place, a
54
54
  * generated job holds no token and its `plan-note` job posts; a job that
55
55
  * still holds one was generated before #4295, and the runner posts for it
56
- * with a warning to regenerate.
56
+ * with a warning to regenerate. Either way the note's overdue check (#4289)
57
+ * reads the scheduled Ops from a fresh checkout of `--base`, as the
58
+ * plan-note job reads them at the target branch's commit (#4328).
57
59
  *
58
60
  * Signing is opt-in (#4156). With no runner key the record is unsigned, and
59
61
  * a reader counts it with no signer. A key `.chant/trust.json` does not list
@@ -81,14 +83,16 @@
81
83
  */
82
84
 
83
85
  import { execFileSync } from "node:child_process";
84
- import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
86
+ import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
85
87
  import { tmpdir } from "node:os";
86
- import { dirname, join, relative, resolve, sep } from "node:path";
88
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
89
+ import { fileURLToPath } from "node:url";
87
90
  import { PR_APPLY_RECORD, PR_ENV_POST_JOB, PR_ENV_REPORT_DIR, PR_LOOP_REPORT_DIR, prEnvJobs, prLoopJobs, prLoopSetup, PR_LOOP_IMAGE, type PrEnvJob, type PrLoopJob, type PrLoopPipelineOptions } from "../components/pr-pipeline";
88
91
  import { detectForge, ForgeEnvironmentError, forgeFromEnv, FORGE_KINDS, type CommitStatus, type ForgeKind, type PrForge } from "../pr-forge";
89
92
  import { holdsForgeToken, withoutForgeTokens } from "../ci-handover";
90
93
  import { postPlanReport } from "../pr-post";
91
- import { PR_APPLY_GATE } from "../pr-loop";
94
+ import { overdueOpsAt } from "../op/schedule-overdue";
95
+ import { PR_APPLY_GATE, type PrOverdue } from "../pr-loop";
92
96
  import type { CommandContext } from "../cli/registry";
93
97
  import { formatError, formatWarning } from "../cli/format";
94
98
  import { CHANT_CI_RUN_FORGE_ENV, CHANT_CI_RUN_ID_ENV, FORGE_RUN_ENV, resolveCiJob, type RunOrigin } from "../lifecycle/release-ledger";
@@ -738,7 +742,9 @@ export async function ciRun(req: CiRunRequest): Promise<CiRunResult> {
738
742
  // A generated wave job's command, as prLoopWaveJobs writes it. #4161: a local job's command posts no status or note.
739
743
  const forgeCommand = req.noForge ? found.command.replace(` --forge ${req.forge}`, "") : found.command;
740
744
  const noStatus = req.local && !/ --no-status(?: |$)/.test(forgeCommand);
741
- const command = `${forgeCommand}${req.wave !== undefined ? ` --wave ${req.wave}` : ""}${noStatus ? " --no-status" : ""}`;
745
+ // #4325: a record or apply on a routed branch is told the branch, as the forge's ref would tell it in a pipeline. `ci serve` has no such ref.
746
+ const branch = req.job !== "plan" && req.loop?.branch !== undefined && !/ --branch(?: |=)/.test(forgeCommand) ? ` --branch ${/^[A-Za-z0-9._\/=:@%+,-]+$/.test(req.loop.branch) ? req.loop.branch : `'${req.loop.branch.replace(/'/g, `'\\''`)}'`}` : "";
747
+ const command = `${forgeCommand}${branch}${req.wave !== undefined ? ` --wave ${req.wave}` : ""}${noStatus ? " --no-status" : ""}`;
742
748
  const job = { ...found, command, ...(req.wave !== undefined ? { wave: req.wave } : {}) };
743
749
  const own = ciJobEnv(job, {
744
750
  forge: req.forge,
@@ -759,7 +765,7 @@ export async function ciRun(req: CiRunRequest): Promise<CiRunResult> {
759
765
  const redact = ciRedactor(req.inPlace ? { ...longValues(jobEnv), ...own } : jobEnv, req.secrets);
760
766
  try {
761
767
  const ran = await runCiJob(req, { cwd, top, commit, place, job, jobEnv, ...(redact ? { redact } : {}) });
762
- const posted = poster ? await postFromReport(req, poster, ran, { commit, ...(place.member ? { member: place.member } : {}) }) : [];
768
+ const posted = poster ? await postFromReport(req, poster, ran, { top, dir: place.dir, commit, ...(place.member ? { member: place.member } : {}) }) : [];
763
769
  const envPost = req.job === "env-up" ? await envPostAfterUp(req, { cwd, top, head: commit, place, jobs, hostEnv, ran }) : undefined;
764
770
  const withPost = envPost ? ("result" in envPost ? { ...ran, post: envPost.result } : { ...ran, warnings: [...ran.warnings, envPost.warning] }) : ran;
765
771
  const withPosts = posted.length > 0 ? { ...withPost, warnings: [...withPost.warnings, ...posted] } : withPost;
@@ -831,10 +837,11 @@ async function postFromReport(
831
837
  req: CiRunRequest,
832
838
  poster: NonNullable<ReturnType<typeof planPoster>>,
833
839
  ran: CiRunResult,
834
- at: { commit: string; member?: string },
840
+ at: { top: string; dir: string; commit: string; member?: string },
835
841
  ): Promise<string[]> {
836
842
  if ("error" in poster) return [poster.error];
837
843
  const warnings = poster.warning ? [poster.warning] : [];
844
+ const checked: string[] = [];
838
845
  const dir = ran.report ?? resolve(req.cwd, req.output ?? PR_LOOP_REPORT_DIR);
839
846
  const posted = await postPlanReport({
840
847
  forge: poster.forge,
@@ -844,9 +851,62 @@ async function postFromReport(
844
851
  env: req.env,
845
852
  ...(at.member ? { member: at.member } : {}),
846
853
  result: ran.status === "ok" ? "success" : "failure",
854
+ // #4328: as pr-post does (#4289), with the Ops read at --base, never from the change's checkout.
855
+ overdue: () => overdueAtBase(req, { forge: poster.forge, top: at.top, dir: at.dir, ...(at.member ? { member: at.member } : {}), log: (line) => checked.push(line) }),
847
856
  });
848
857
  if (posted.refused) warnings.push(`the plan report was not posted: ${posted.refused}`);
849
- return [...warnings, ...posted.warnings];
858
+ return [...warnings, ...checked, ...posted.warnings];
859
+ }
860
+
861
+ /**
862
+ * The overdue check for the plan's note (#4289, #4328), at `--base`, as the
863
+ * plan-note job's `pr-post` runs it at the target branch's commit. The Ops
864
+ * are read from a fresh checkout of the base outside the repository, never
865
+ * from the checkout the change's code ran in, in place or not. That checkout
866
+ * resolves packages from the `node_modules` the running chant is installed
867
+ * in ({@link chantNodeModules}), so an Op file's `@intentius/chant` is the
868
+ * chant running the job, not one the change installed. Without `--base`
869
+ * nothing is checked, and `log` says so.
870
+ */
871
+ async function overdueAtBase(
872
+ req: CiRunRequest,
873
+ at: { forge: PrForge; top: string; dir: string; member?: string; log: (line: string) => void },
874
+ ): Promise<PrOverdue[]> {
875
+ if (!at.forge.scheduledRuns) return [];
876
+ if (!req.base) {
877
+ at.log("the scheduled Ops were not checked: the check reads them at the target branch's commit, so pass --base <sha>");
878
+ return [];
879
+ }
880
+ let checkout: { dir: string; remove(): void };
881
+ try {
882
+ checkout = freshCheckout(at.top, resolveJobCommit(at.top, req.base));
883
+ } catch (err) {
884
+ at.log(`the scheduled Ops were not checked: ${message(err)}`);
885
+ return [];
886
+ }
887
+ try {
888
+ const modules = chantNodeModules();
889
+ const link = join(checkout.dir, "node_modules");
890
+ if (modules && !existsSync(link)) symlinkSync(modules, link, "dir");
891
+ return await overdueOpsAt({ forge: at.forge, cwd: at.dir === "." ? checkout.dir : join(checkout.dir, at.dir), ...(at.member ? { member: at.member } : {}), log: at.log });
892
+ } finally {
893
+ checkout.remove();
894
+ }
895
+ }
896
+
897
+ /**
898
+ * The `node_modules` directory the running chant is installed in, or
899
+ * undefined when it runs from a source checkout, where chant's own packages
900
+ * resolve without one (../ts-loader.ts).
901
+ */
902
+ export function chantNodeModules(from: string = fileURLToPath(import.meta.url)): string | undefined {
903
+ let dir = dirname(from);
904
+ for (;;) {
905
+ if (basename(dir) === "node_modules") return dir;
906
+ const parent = dirname(dir);
907
+ if (parent === dir) return undefined;
908
+ dir = parent;
909
+ }
850
910
  }
851
911
 
852
912
  /**
@@ -602,3 +602,98 @@ describe("ci serve runs fast-forwards only, and honours [skip ci] (#4122)", () =
602
602
  expect(ofType(events, "not-run")).toHaveLength(1);
603
603
  });
604
604
  });
605
+
606
+ describe("ci serve follows ci.branches (#4325)", () => {
607
+ /** A forge with a head per branch, and the branches it was asked about. */
608
+ function branchForge(heads: Record<string, string>): CiServeForge & { heads: Record<string, string>; polled: string[] } {
609
+ const f = {
610
+ heads,
611
+ polled: [] as string[],
612
+ async pullRequestFor() {
613
+ return null;
614
+ },
615
+ async openPullRequests(base: string) {
616
+ f.polled.push(base);
617
+ return [];
618
+ },
619
+ async branchHead(branch: string) {
620
+ return f.heads[branch];
621
+ },
622
+ };
623
+ return f;
624
+ }
625
+
626
+ /** The workspace with `ci.branches` routing `app` to `release`, and a `release` branch on the forge. */
627
+ function routed(globs: string[] = ["app"]): ReturnType<typeof workspace> & { release: string } {
628
+ const w = workspace();
629
+ writeFiles(w.root, { "chant.workspace.json": declaration([{ name: "app", dir: "app", kind: "other", because: "the app" }], { ci: { branches: { release: globs } } }) });
630
+ commit(w.root, "app/route.txt", "route app to release");
631
+ git(w.root, "push", "-q", "origin", "main");
632
+ git(w.root, "checkout", "-q", "-b", "release");
633
+ const release = commit(w.root, "app/rel.txt");
634
+ git(w.root, "push", "-q", "origin", "release");
635
+ git(w.root, "checkout", "-q", "main");
636
+ return { ...w, main: git(w.root, "rev-parse", "main"), release };
637
+ }
638
+
639
+ test("a routed member polls its route alone, and its record job gets --branch", async () => {
640
+ const { root, app, main, release } = routed();
641
+ const forge = branchForge({ main, release });
642
+ const { opts, events } = serve(app, forge);
643
+ await ciServePass(opts);
644
+ expect(forge.polled).toEqual(["release"]);
645
+ expect(ofType(events, "baseline")).toEqual([{ type: "baseline", commit: release }]);
646
+
647
+ git(root, "checkout", "-q", "release");
648
+ const next = commit(root, "app/rel2.txt");
649
+ git(root, "push", "-q", "origin", "release");
650
+ git(root, "checkout", "-q", "main");
651
+ forge.heads.release = next;
652
+ const pass = await ciServePass(opts);
653
+ expect(pass.jobs).toEqual([expect.objectContaining({ job: "record", commit: next, base: release, status: "ok" })]);
654
+ const argv = argsOf(pass.jobs[0]!.report!, "pr-record");
655
+ expect(argv[argv.indexOf("--branch") + 1]).toBe("release");
656
+ // A push to main is not this member's.
657
+ forge.heads.main = "0".repeat(40);
658
+ expect((await ciServePass(opts)).jobs).toEqual([]);
659
+ });
660
+
661
+ test("a member no glob matches polls main and passes no --branch", async () => {
662
+ const { root, app, main } = routed(["other-*"]);
663
+ const forge = branchForge({ main });
664
+ const { opts } = serve(app, forge);
665
+ await ciServePass(opts);
666
+ expect(forge.polled).toEqual(["main"]);
667
+ const merged = commit(root, "app/merged.txt");
668
+ git(root, "push", "-q", "origin", "main");
669
+ forge.heads.main = merged;
670
+ const pass = await ciServePass(opts);
671
+ expect(pass.jobs).toEqual([expect.objectContaining({ job: "record", commit: merged, base: main })]);
672
+ expect(argsOf(pass.jobs[0]!.report!, "pr-record")).not.toContain("--branch");
673
+ });
674
+
675
+ test("the workspace root polls main and each routed branch, each from its own head", async () => {
676
+ const { root, main, release } = routed();
677
+ const forge = branchForge({ main, release });
678
+ const { opts } = serve(root, forge);
679
+ await ciServePass(opts);
680
+ expect(forge.polled).toEqual(["main", "release"]);
681
+ git(root, "checkout", "-q", "release");
682
+ const next = commit(root, "app/rel2.txt");
683
+ git(root, "push", "-q", "origin", "release");
684
+ git(root, "checkout", "-q", "main");
685
+ forge.heads.release = next;
686
+ const pass = await ciServePass(opts);
687
+ expect(pass.jobs).toEqual([expect.objectContaining({ job: "record", commit: next, base: release })]);
688
+ const state = readCiServeState(join(opts.stateDir!, `${prApplyGroup("prod")}.json`), { forge: "forgejo", repo: "demo/estate", env: "prod", branch: "main" });
689
+ expect(state.target).toBe(main);
690
+ expect(state.targets).toEqual({ release: next });
691
+ expect((await ciServePass(opts)).jobs).toEqual([]);
692
+ });
693
+
694
+ test("a --branch that disagrees with the route is refused", async () => {
695
+ const { app, main, release } = routed();
696
+ const { opts } = serve(app, branchForge({ main, release }), { job: { loop: { branch: "main" } } });
697
+ await expect(ciServePass(opts)).rejects.toThrow(/routes member app to release/);
698
+ });
699
+ });
@@ -59,6 +59,11 @@
59
59
  * contains the other. Each is a warning. A local branch ahead of the
60
60
  * forge's keeps its facts, and the job's push sends them. Nothing here
61
61
  * pushes the ledger.
62
+ * - Routed branches (#4325). `ci.branches` decides the branches one serve
63
+ * polls: a routed member's route alone, an unrouted member's `--branch`
64
+ * (default `main`), and at the workspace root `main` and each routed
65
+ * branch. Jobs on a routed branch get `--branch`. The state file keeps a
66
+ * head per branch (`target`, then `targets`).
62
67
  *
63
68
  * - Land branches (#4168 4.4). With `land`, each `chant/land/<name>` branch
64
69
  * pushed to the remote is a change asking to land without a pull request.
@@ -104,6 +109,7 @@ import { CI_JOB_OPS, ciJobPlace, ciLoopOptions, ciSecretsOf, ciRun, ciStatusFrom
104
109
  import { CiGreenError, ciTick, judgePreMerge, lastGreen, RUNNER_KEY_ENV, signedRunsRequired, type CommitVerdict } from "./ci-green";
105
110
  import { gitTop } from "./tree";
106
111
  import { readDeclaration } from "./declaration";
112
+ import { servedBranches } from "./ci-branches";
107
113
  import { locateWorkspace } from "./which-chant";
108
114
  import { ciServeRunnerKey, formatRunnerKeySetup, setupRunnerKey } from "./ci-runner-key";
109
115
 
@@ -124,8 +130,8 @@ export interface CiServeForge {
124
130
  * apply of the newest green commit (#4168 step 9).
125
131
  */
126
132
  export type CiServeTrigger =
127
- | { job: "plan"; commit: string; base: string; pr: number; rerun?: boolean }
128
- | { job: "apply" | "record"; commit: string; base: string; rerun?: boolean };
133
+ | { job: "plan"; commit: string; base: string; pr: number; rerun?: boolean; branch?: string }
134
+ | { job: "apply" | "record"; commit: string; base: string; rerun?: boolean; branch?: string };
129
135
 
130
136
  /**
131
137
  * The `git` forge kind (#4016 gap 6): the branch head from `git ls-remote`
@@ -193,6 +199,8 @@ export interface CiServeState {
193
199
  member?: string;
194
200
  /** The target branch's head the last apply was measured to, or the last head seen and not applied. */
195
201
  target?: string;
202
+ /** The same for each further branch `ci.branches` routes (#4325), by branch. `target` is the first served branch's. */
203
+ targets?: Record<string, string>;
196
204
  jobs: Record<string, CiServeJobState>;
197
205
  /** Plans seen and not run, by state key, with why (#4122). */
198
206
  skipped?: Record<string, "skip-ci">;
@@ -381,7 +389,12 @@ export function defaultCiServeStateDir(cwd: string): string {
381
389
  interface ServePlace {
382
390
  top: string;
383
391
  member?: string;
392
+ /** The first branch served: the one the state file's `target` follows. */
384
393
  branch: string;
394
+ /** Every branch served (#4325), `branch` first: a routed member's route, or at the workspace root the default and each routed branch. */
395
+ branches: string[];
396
+ /** The served branches a job is told with `--branch`: the ones `ci.branches` names. */
397
+ routed: string[];
385
398
  group: string;
386
399
  stateDir: string;
387
400
  stateFile: string;
@@ -394,7 +407,21 @@ function servePlace(opts: CiServeOptions): ServePlace {
394
407
  const { member } = ciJobPlace(cwd, top);
395
408
  const group = prApplyGroup(opts.env, member);
396
409
  const stateDir = opts.stateDir ? resolve(cwd, opts.stateDir) : defaultCiServeStateDir(cwd);
397
- return { top, ...(member ? { member } : {}), branch: opts.job?.loop?.branch ?? "main", group, stateDir, stateFile: join(stateDir, `${group}.json`) };
410
+ // #4325: ci.branches decides which branches this serve polls. A workspace that can't be read routes nothing.
411
+ let routes: Record<string, string[]> | undefined;
412
+ try {
413
+ routes = readDeclaration(locateWorkspace(cwd).tree).ci?.branches;
414
+ } catch {
415
+ routes = undefined;
416
+ }
417
+ const named = opts.job?.loop?.branch;
418
+ let served: ReturnType<typeof servedBranches>;
419
+ try {
420
+ served = servedBranches(routes, member, named ?? "main", named !== undefined);
421
+ } catch (err) {
422
+ throw new CiRunError(message(err));
423
+ }
424
+ return { top, ...(member ? { member } : {}), branch: served.served[0]!, branches: served.served, routed: served.routed, group, stateDir, stateFile: join(stateDir, `${group}.json`) };
398
425
  }
399
426
 
400
427
  /** The state file, or a new one. */
@@ -439,73 +466,81 @@ export async function ciServeObserve(opts: CiServeOptions, rt: CiServeRuntime =
439
466
  };
440
467
  const triggers: CiServeTrigger[] = [];
441
468
  const events: CiServeEvent[] = [];
442
- const [head, prs] = await Promise.all([opts.client.branchHead(place.branch), opts.client.openPullRequests(place.branch)]);
443
469
  // #4168: a new target commit is recorded. With applyOnMerge the newest green commit is applied, below.
444
470
  const onMerge = "record" as const;
471
+ const authors = opts.authors?.length ? new Set(opts.authors.map((a) => a.toLowerCase())) : undefined;
445
472
 
446
- if (head === undefined) {
447
- events.push({ type: "no-branch", branch: place.branch });
448
- } else if (state.target === undefined) {
449
- // What the branch held before this runner watched it was applied by whatever ran then.
450
- update((s) => {
451
- s.target = head;
452
- });
453
- events.push({ type: "baseline", commit: head });
454
- } else if (head !== state.target) {
455
- const from = state.target;
456
- const move = targetMove(place.top, place.branch, from, head);
457
- const entry = state.jobs[ciServeKey({ job: onMerge, commit: head })];
458
- if (move === "unknown") {
459
- events.push({ type: "error", message: `${place.branch} is at ${head.slice(0, 12)} on the forge, and that commit could not be fetched; the next poll tries again` });
460
- } else if (move === "other" || (!entry && skipsCi(place.top, head))) {
461
- // #4122: a move that is not a fast-forward, or a head that asks for no run, is seen and not applied.
462
- update((s) => {
463
- s.target = head;
464
- });
465
- events.push({ type: "not-run", job: onMerge, commit: head, reason: move === "other" ? "not-fast-forward" : "skip-ci", ...(move === "other" ? { branch: place.branch, from } : {}) });
466
- } else if (!entry) triggers.push({ job: onMerge, commit: head, base: from });
467
- else if (opts.rerunFailed && failedEntry(entry, ciServeKey(entry), rt)) triggers.push({ job: onMerge, commit: head, base: entry.base, rerun: true });
468
- } else {
469
- const key = ciServeKey({ job: onMerge, commit: head });
470
- const entry = state.jobs[key];
471
- if (opts.rerunFailed && failedEntry(entry, key, rt)) triggers.push({ job: onMerge, commit: head, base: entry.base, rerun: true });
472
- }
473
+ // #4325: each served branch is polled on its own, with its own head to measure moves from.
474
+ for (const branch of place.branches) {
475
+ const [head, prs] = await Promise.all([opts.client.branchHead(branch), opts.client.openPullRequests(branch)]);
476
+ const first = branch === place.branch;
477
+ const target = first ? state.target : state.targets?.[branch];
478
+ const setTarget = (s: CiServeState, sha: string): void => {
479
+ if (first) s.target = sha;
480
+ else s.targets = { ...s.targets, [branch]: sha };
481
+ };
482
+ // A job on a routed branch is told which, so its apply routes members as the pushed branch does.
483
+ const routed = place.routed.includes(branch) ? { branch } : {};
473
484
 
474
- if (opts.applyOnMerge && head !== undefined) {
475
- const green = newestGreen(place.top, opts.cwd, place.branch);
476
- if (green) {
477
- const key = ciServeKey({ job: "apply", commit: green });
485
+ if (head === undefined) {
486
+ events.push({ type: "no-branch", branch });
487
+ } else if (target === undefined) {
488
+ // What the branch held before this runner watched it was applied by whatever ran then.
489
+ update((s) => setTarget(s, head));
490
+ events.push({ type: "baseline", commit: head });
491
+ } else if (head !== target) {
492
+ const from = target;
493
+ const move = targetMove(place.top, branch, from, head);
494
+ const entry = state.jobs[ciServeKey({ job: onMerge, commit: head })];
495
+ if (move === "unknown") {
496
+ events.push({ type: "error", message: `${branch} is at ${head.slice(0, 12)} on the forge, and that commit could not be fetched; the next poll tries again` });
497
+ } else if (move === "other" || (!entry && skipsCi(place.top, head))) {
498
+ // #4122: a move that is not a fast-forward, or a head that asks for no run, is seen and not applied.
499
+ update((s) => setTarget(s, head));
500
+ events.push({ type: "not-run", job: onMerge, commit: head, reason: move === "other" ? "not-fast-forward" : "skip-ci", ...(move === "other" ? { branch, from } : {}) });
501
+ } else if (!entry) triggers.push({ job: onMerge, commit: head, base: from, ...routed });
502
+ else if (opts.rerunFailed && failedEntry(entry, ciServeKey(entry), rt)) triggers.push({ job: onMerge, commit: head, base: entry.base, rerun: true, ...routed });
503
+ } else {
504
+ const key = ciServeKey({ job: onMerge, commit: head });
478
505
  const entry = state.jobs[key];
479
- if (!entry) triggers.push({ job: "apply", commit: green, base: green });
480
- else if (opts.rerunFailed && failedEntry(entry, key, rt)) triggers.push({ job: "apply", commit: green, base: entry.base, rerun: true });
506
+ if (opts.rerunFailed && failedEntry(entry, key, rt)) triggers.push({ job: onMerge, commit: head, base: entry.base, rerun: true, ...routed });
481
507
  }
482
- }
483
508
 
484
- const authors = opts.authors?.length ? new Set(opts.authors.map((a) => a.toLowerCase())) : undefined;
485
- for (const pr of prs) {
486
- if (pr.fork) {
487
- events.push({ type: "skipped", pr: pr.number, reason: "fork" });
488
- continue;
489
- }
490
- if (authors && !authors.has((pr.author ?? "").toLowerCase())) {
491
- events.push({ type: "skipped", pr: pr.number, reason: "author", ...(pr.author ? { author: pr.author } : {}) });
492
- continue;
509
+ if (opts.applyOnMerge && head !== undefined) {
510
+ const green = newestGreen(place.top, opts.cwd, branch, place.routed.includes(branch));
511
+ if (green) {
512
+ const key = ciServeKey({ job: "apply", commit: green });
513
+ const entry = state.jobs[key];
514
+ if (!entry) triggers.push({ job: "apply", commit: green, base: green, ...routed });
515
+ else if (opts.rerunFailed && failedEntry(entry, key, rt)) triggers.push({ job: "apply", commit: green, base: entry.base, rerun: true, ...routed });
516
+ }
493
517
  }
494
- const key = ciServeKey({ job: "plan", commit: pr.headSha, pr: pr.number });
495
- const entry = state.jobs[key];
496
- const base = pr.baseSha ?? head;
497
- if (!base || state.skipped?.[key]) continue;
498
- if (!entry) {
499
- // #4122: a head whose message asks for no run is seen and not planned.
500
- if (gitStatus(place.top, ["cat-file", "-e", `${pr.headSha}^{commit}`]).code !== 0) tryGit(place.top, ["fetch", "--quiet", "--no-tags", "origin", `refs/pull/${pr.number}/head`]);
501
- if (skipsCi(place.top, pr.headSha)) {
502
- update((s) => {
503
- s.skipped = { ...s.skipped, [key]: "skip-ci" };
504
- });
505
- events.push({ type: "not-run", job: "plan", commit: pr.headSha, pr: pr.number, reason: "skip-ci" });
506
- } else triggers.push({ job: "plan", commit: pr.headSha, base, pr: pr.number });
518
+
519
+ for (const pr of prs) {
520
+ if (pr.fork) {
521
+ events.push({ type: "skipped", pr: pr.number, reason: "fork" });
522
+ continue;
523
+ }
524
+ if (authors && !authors.has((pr.author ?? "").toLowerCase())) {
525
+ events.push({ type: "skipped", pr: pr.number, reason: "author", ...(pr.author ? { author: pr.author } : {}) });
526
+ continue;
527
+ }
528
+ const key = ciServeKey({ job: "plan", commit: pr.headSha, pr: pr.number });
529
+ const entry = state.jobs[key];
530
+ const base = pr.baseSha ?? head;
531
+ if (!base || state.skipped?.[key]) continue;
532
+ if (!entry) {
533
+ // #4122: a head whose message asks for no run is seen and not planned.
534
+ if (gitStatus(place.top, ["cat-file", "-e", `${pr.headSha}^{commit}`]).code !== 0) tryGit(place.top, ["fetch", "--quiet", "--no-tags", "origin", `refs/pull/${pr.number}/head`]);
535
+ if (skipsCi(place.top, pr.headSha)) {
536
+ update((s) => {
537
+ s.skipped = { ...s.skipped, [key]: "skip-ci" };
538
+ });
539
+ events.push({ type: "not-run", job: "plan", commit: pr.headSha, pr: pr.number, reason: "skip-ci" });
540
+ } else triggers.push({ job: "plan", commit: pr.headSha, base, pr: pr.number, ...routed });
541
+ }
542
+ else if (opts.rerunFailed && failedEntry(entry, key, rt)) triggers.push({ job: "plan", commit: pr.headSha, base: entry.base, pr: pr.number, rerun: true, ...routed });
507
543
  }
508
- else if (opts.rerunFailed && failedEntry(entry, key, rt)) triggers.push({ job: "plan", commit: pr.headSha, base: entry.base, pr: pr.number, rerun: true });
509
544
  }
510
545
  return { triggers, events };
511
546
  }
@@ -581,7 +616,10 @@ export async function ciServeSchedule(opts: CiServeOptions, triggers: readonly C
581
616
  const entry: CiServeJobState = { job: t.job, commit: t.commit, base: t.base, ...pr, status: "running", runId, attempts: attempt, startedAt: now().toISOString() };
582
617
  update((s) => {
583
618
  s.jobs[key] = entry;
584
- if (t.job === "record" && !t.rerun) s.target = t.commit;
619
+ if (t.job === "record" && !t.rerun) {
620
+ if (t.branch === undefined || t.branch === place.branch) s.target = t.commit;
621
+ else s.targets = { ...s.targets, [t.branch]: t.commit };
622
+ }
585
623
  });
586
624
  emit({ type: "started", job: t.job, commit: t.commit, ...pr, base: t.base, runId, attempt, ...(resume && existsSync(resume) ? { resume } : {}) });
587
625
 
@@ -621,13 +659,15 @@ async function runJob(
621
659
  let merged: number | undefined;
622
660
  try {
623
661
  // The pull request's head is on the forge's refs/pull/<n>/head, the target's on the branch.
624
- tryGit(place.top, ["fetch", "--quiet", "--no-tags", "origin", t.job === "plan" ? `refs/pull/${t.pr}/head` : place.branch]);
662
+ tryGit(place.top, ["fetch", "--quiet", "--no-tags", "origin", t.job === "plan" ? `refs/pull/${t.pr}/head` : (t.branch ?? place.branch)]);
625
663
  const hostEnv = { ...(opts.job?.hostEnv ?? process.env), GITHUB_REPOSITORY: opts.repo };
626
664
  // An apply is for a merge: pass the pull request whose merge commit it is, so the commit status link and the run index use `<pr>/<commit>`. None found, or a forge that errs, is today's behaviour.
627
665
  if (t.job === "record" && opts.client.pullRequestFor) merged = (await opts.client.pullRequestFor(t.commit).catch(() => null)) ?? undefined;
628
666
  const run = opts.run ?? ciRun;
629
667
  const r = await run({
630
668
  ...opts.job,
669
+ // #4325: a job on a routed branch is told the branch, so it routes members as a push to it does.
670
+ ...(t.branch !== undefined ? { loop: { ...opts.job?.loop, branch: t.branch } } : {}),
631
671
  hostEnv,
632
672
  cwd: opts.cwd,
633
673
  job: t.job,
@@ -656,10 +696,11 @@ async function runJob(
656
696
  * fetching the ci tags, or undefined when none is green or the workspace
657
697
  * declares no `ci.green`.
658
698
  */
659
- function newestGreen(top: string, cwd: string, branch: string): string | undefined {
699
+ function newestGreen(top: string, cwd: string, branch: string, routed = false): string | undefined {
660
700
  tryGit(top, ["fetch", "--quiet", "origin", `+refs/heads/${branch}:refs/remotes/origin/${branch}`, "+refs/tags/ci/*:refs/tags/ci/*"]);
661
701
  try {
662
- const doc = lastGreen({ cwd });
702
+ // #4325: a routed branch has its own verdict, and none when ci.green does not name it.
703
+ const doc = lastGreen({ cwd, ...(routed ? { branch } : {}) });
663
704
  return "commit" in doc && doc.commit ? doc.commit.sha : undefined;
664
705
  } catch {
665
706
  return undefined;
@@ -14,9 +14,12 @@ import Ajv2020 from "ajv/dist/2020";
14
14
  import { afterEach, beforeEach, describe, expect, test } from "vitest";
15
15
  import { computeComponentGraph } from "../components/cli-support";
16
16
  import componentSchema from "../components/component.schema.json";
17
+ import { ChantConfigSchema, environmentExecutor } from "../config";
17
18
  import { componentEnvironments, memberEnvironments, type EnvironmentReason } from "./environments";
18
19
  import type { MemberRuntimes } from "./runtimes";
19
20
 
21
+ const NO_EXECUTOR = { forge: null, workflow: null, reason: { code: "executor-none-declared", message: expect.any(String) } };
22
+
20
23
  const runtimes = (environments: MemberRuntimes["environments"], lexicons: string[] = []): MemberRuntimes => ({ lexicons, default: "local", reasons: [], environments });
21
24
 
22
25
  const SITES = [
@@ -68,13 +71,14 @@ describe("graph --composites lists each environment with the component's site (#
68
71
  const member = memberEnvironments(runtimes(["prod", "fly"], ["fly"]), { envs: [], reason: null });
69
72
  const envs = componentEnvironments("app", member, "local", SITES, ["fly"]);
70
73
  expect(envs).toEqual([
71
- { name: "local", default: true, source: "builtin", site: null, command: "chant run --components app" },
72
- { name: "prod", default: false, source: "config", site: { runtime: "local", url: null, domain: null, lifecycle: null }, command: "chant run --components app --env prod" },
74
+ { name: "local", default: true, source: "builtin", site: null, executor: NO_EXECUTOR, command: "chant run --components app" },
75
+ { name: "prod", default: false, source: "config", site: { runtime: "local", url: null, domain: null, lifecycle: null }, executor: NO_EXECUTOR, command: "chant run --components app --env prod" },
73
76
  {
74
77
  name: "fly",
75
78
  default: false,
76
79
  source: "config",
77
80
  site: { runtime: "fly", url: "https://shop.fly.dev", domain: "shop.example.com", lifecycle: "origin" },
81
+ executor: NO_EXECUTOR,
78
82
  command: "chant run --components app --on fly --env fly",
79
83
  },
80
84
  ]);
@@ -103,3 +107,22 @@ describe("graph --composites lists each environment with the component's site (#
103
107
  expect(envs.find((e) => e.name === "pr-7")).toMatchObject({ source: "component", site: { url: "https://pr-7.example.com" } });
104
108
  });
105
109
  });
110
+
111
+ describe("an environment's executor in chant.config.ts (#4402)", () => {
112
+ const prod = { name: "prod", executor: { forge: "github" as const, workflow: "deploy-prod.yml" } };
113
+
114
+ test("is optional on an environment entry, and refuses an unknown forge, an empty workflow and a stray field", () => {
115
+ expect(ChantConfigSchema.safeParse({ environments: ["staging", prod] }).success).toBe(true);
116
+ expect(ChantConfigSchema.safeParse({ environments: [{ name: "prod", executor: { forge: "jenkins", workflow: "x.yml" } }] }).success).toBe(false);
117
+ expect(ChantConfigSchema.safeParse({ environments: [{ name: "prod", executor: { forge: "github", workflow: "" } }] }).success).toBe(false);
118
+ expect(ChantConfigSchema.safeParse({ environments: [{ name: "prod", executor: { forge: "github", workflow: "x.yml", ref: "main" } }] }).success).toBe(false);
119
+ });
120
+
121
+ test("resolves a literal entry before a pattern, and a literal without one doesn't take the pattern's", () => {
122
+ const envs = [prod, "pr-special", { name: "pr-*", executor: { forge: "gitlab" as const, workflow: ".gitlab-ci.yml" } }];
123
+ expect(environmentExecutor(envs, "prod")).toEqual(prod.executor);
124
+ expect(environmentExecutor(envs, "pr-7")).toEqual({ forge: "gitlab", workflow: ".gitlab-ci.yml" });
125
+ expect(environmentExecutor(envs, "pr-special")).toBeUndefined();
126
+ expect(environmentExecutor(envs, "staging")).toBeUndefined();
127
+ });
128
+ });
@@ -100,6 +100,12 @@ export interface ComposedMember {
100
100
  * the outer `nodes` with `outer/inner/id` ids.
101
101
  */
102
102
  nested?: NestedWorkspace;
103
+ /**
104
+ * For a member of kind `repo` (#3168): the repository and pinned revision it
105
+ * names, and `read`, the repo identity the other repository's own graph
106
+ * document gave, or null. Its nodes fold in as a nested workspace's do.
107
+ */
108
+ repo?: { url: string; rev: string; workspace: string; read: string | null };
103
109
  /**
104
110
  * The factory and listing the member's box block declares (#3146, ws-077),
105
111
  * read from the declaration at the revision read. Set by
@@ -181,7 +187,8 @@ export type ComposedDangling = IRDanglingRef & { member: string; nested?: string
181
187
 
182
188
  export interface WorkspaceGraph {
183
189
  version: number;
184
- workspace: { name: string; root: string };
190
+ /** `repo` (#3168): the repository the document came from, its origin URL redacted, or null; absent from a chant before it. */
191
+ workspace: { name: string; root: string; repo?: string | null };
185
192
  members: ComposedMember[];
186
193
  nodes: ComposedNode[];
187
194
  edges: ComposedEdge[];
@@ -335,7 +342,7 @@ function sorted(rec: Record<string, string[]>): Record<string, string[]> {
335
342
  * `read` holds the links each member's kind reader found in its files (#3932).
336
343
  */
337
344
  export function composeWorkspaceGraph(
338
- workspace: { name: string; root: string },
345
+ workspace: { name: string; root: string; repo?: string | null },
339
346
  inputs: ComposeInput[],
340
347
  links?: { declaration: Declaration; kinds?: KindRegistry; read?: ReadonlyMap<string, KindLinkRead> },
341
348
  ): WorkspaceGraph {
@@ -607,6 +607,28 @@
607
607
  }
608
608
  ]
609
609
  },
610
+ "executor": {
611
+ "description": "The forge and committed workflow that deploys this environment, as the member's chant.config.ts declares on the environment's entry (#4402): a literal entry before a pattern. forge and workflow are null when none is declared, and reason then says why. Never derived from a generated workflow's name. Added by chant 0.124.47; a document from an older chant has no executor.",
612
+ "type": "object",
613
+ "required": ["forge", "workflow", "reason"],
614
+ "properties": {
615
+ "forge": { "enum": ["github", "gitlab", "forgejo", null] },
616
+ "workflow": { "type": ["string", "null"], "description": "The workflow file the forge runs, such as deploy-prod.yml under .github/workflows/, or .gitlab-ci.yml." },
617
+ "reason": {
618
+ "oneOf": [
619
+ { "type": "null" },
620
+ {
621
+ "type": "object",
622
+ "required": ["code", "message"],
623
+ "properties": {
624
+ "code": { "enum": ["executor-none-declared", "executor-config-unreadable"] },
625
+ "message": { "type": "string" }
626
+ }
627
+ }
628
+ ]
629
+ }
630
+ }
631
+ },
610
632
  "command": {
611
633
  "type": "string",
612
634
  "description": "The command that deploys the component to this environment on its default runtime, run in the member's directory: chant run --components <name>, with --on <runtime> when the default runtime isn't local, and --env <env> for any environment but the default."