@intentius/chant 0.124.46 → 0.124.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (726) hide show
  1. package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  2. package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  3. package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  4. package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  6. package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  7. package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
  8. package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
  9. package/dist/__golden__/wave-loop/op-denied.json +16 -0
  10. package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  11. package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  12. package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
  13. package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
  14. package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
  15. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  16. package/dist/__golden__/wave-loop/op-held-on.json +16 -0
  17. package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  18. package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  19. package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  20. package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  21. package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
  22. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  23. package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
  24. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  25. package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
  26. package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
  27. package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  28. package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  29. package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
  30. package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  31. package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  32. package/dist/__golden__/wave-loop/op-overridden.json +16 -0
  33. package/dist/__golden__/wave-loop/op-per-run.json +20 -0
  34. package/dist/__golden__/wave-loop/op-point.json +13 -0
  35. package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
  36. package/dist/__golden__/wave-loop/op-sealed.json +10 -0
  37. package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
  38. package/dist/__golden__/wave-loop/op-spend.json +10 -0
  39. package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
  40. package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
  41. package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
  42. package/dist/__golden__/wave-loop/op-wide.json +39 -0
  43. package/dist/action-pins.d.ts +61 -0
  44. package/dist/action-pins.d.ts.map +1 -0
  45. package/dist/action-pins.js +77 -0
  46. package/dist/cli/commands/carve.d.ts +3 -0
  47. package/dist/cli/commands/carve.d.ts.map +1 -1
  48. package/dist/cli/commands/carve.js +36 -7
  49. package/dist/cli/commands/import-report.d.ts +26 -0
  50. package/dist/cli/commands/import-report.d.ts.map +1 -0
  51. package/dist/cli/commands/import-report.js +65 -0
  52. package/dist/cli/commands/import.d.ts +11 -1
  53. package/dist/cli/commands/import.d.ts.map +1 -1
  54. package/dist/cli/commands/import.js +22 -3
  55. package/dist/cli/commands/migrate.d.ts +13 -1
  56. package/dist/cli/commands/migrate.d.ts.map +1 -1
  57. package/dist/cli/commands/migrate.js +134 -10
  58. package/dist/cli/handlers/approve.d.ts.map +1 -1
  59. package/dist/cli/handlers/approve.js +41 -14
  60. package/dist/cli/handlers/build.d.ts.map +1 -1
  61. package/dist/cli/handlers/build.js +6 -1
  62. package/dist/cli/handlers/graph-reader.d.ts +26 -0
  63. package/dist/cli/handlers/graph-reader.d.ts.map +1 -0
  64. package/dist/cli/handlers/graph-reader.js +41 -0
  65. package/dist/cli/handlers/graph.d.ts.map +1 -1
  66. package/dist/cli/handlers/graph.js +14 -0
  67. package/dist/cli/handlers/ledger.d.ts +3 -0
  68. package/dist/cli/handlers/ledger.d.ts.map +1 -0
  69. package/dist/cli/handlers/ledger.js +55 -0
  70. package/dist/cli/handlers/migrate.d.ts.map +1 -1
  71. package/dist/cli/handlers/migrate.js +18 -11
  72. package/dist/cli/handlers/misc.d.ts.map +1 -1
  73. package/dist/cli/handlers/misc.js +12 -1
  74. package/dist/cli/handlers/pr-env.js +2 -2
  75. package/dist/cli/handlers/pr.d.ts +15 -1
  76. package/dist/cli/handlers/pr.d.ts.map +1 -1
  77. package/dist/cli/handlers/pr.js +183 -40
  78. package/dist/cli/handlers/query.d.ts +7 -2
  79. package/dist/cli/handlers/query.d.ts.map +1 -1
  80. package/dist/cli/handlers/query.js +16 -4
  81. package/dist/cli/handlers/release.d.ts.map +1 -1
  82. package/dist/cli/handlers/release.js +6 -3
  83. package/dist/cli/handlers/search.d.ts +2 -4
  84. package/dist/cli/handlers/search.d.ts.map +1 -1
  85. package/dist/cli/handlers/search.js +13 -39
  86. package/dist/cli/main.d.ts.map +1 -1
  87. package/dist/cli/main.js +87 -14
  88. package/dist/cli/parse-args.d.ts.map +1 -1
  89. package/dist/cli/parse-args.js +47 -2
  90. package/dist/cli/registry.d.ts +22 -4
  91. package/dist/cli/registry.d.ts.map +1 -1
  92. package/dist/components/builders.d.ts +5 -1
  93. package/dist/components/builders.d.ts.map +1 -1
  94. package/dist/components/builders.js +5 -0
  95. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  96. package/dist/components/capability-plugin-loader.js +16 -1
  97. package/dist/components/capability-plugin.d.ts +14 -0
  98. package/dist/components/capability-plugin.d.ts.map +1 -1
  99. package/dist/components/pr-hooks.d.ts +21 -1
  100. package/dist/components/pr-hooks.d.ts.map +1 -1
  101. package/dist/components/pr-hooks.js +72 -4
  102. package/dist/components/pr-pipeline.d.ts +45 -10
  103. package/dist/components/pr-pipeline.d.ts.map +1 -1
  104. package/dist/components/pr-pipeline.js +47 -14
  105. package/dist/components/pr-policy.d.ts +36 -0
  106. package/dist/components/pr-policy.d.ts.map +1 -1
  107. package/dist/components/pr-policy.js +36 -0
  108. package/dist/components/pr-run.d.ts +3 -0
  109. package/dist/components/pr-run.d.ts.map +1 -1
  110. package/dist/components/pr-run.js +1 -0
  111. package/dist/components/starter-plugin.d.ts +1 -0
  112. package/dist/components/starter-plugin.d.ts.map +1 -1
  113. package/dist/components/starter-plugin.js +5 -1
  114. package/dist/components/verbs/index.d.ts +2 -0
  115. package/dist/components/verbs/index.d.ts.map +1 -1
  116. package/dist/components/verbs/index.js +2 -0
  117. package/dist/components/verbs/publish-artifact.d.ts +65 -0
  118. package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
  119. package/dist/components/verbs/publish-artifact.js +74 -0
  120. package/dist/components/verbs/publish-image.d.ts +129 -0
  121. package/dist/components/verbs/publish-image.d.ts.map +1 -0
  122. package/dist/components/verbs/publish-image.js +145 -0
  123. package/dist/config-environments.d.ts +20 -0
  124. package/dist/config-environments.d.ts.map +1 -1
  125. package/dist/config-environments.js +12 -0
  126. package/dist/config.d.ts +25 -1
  127. package/dist/config.d.ts.map +1 -1
  128. package/dist/config.js +10 -2
  129. package/dist/gate-decision.d.ts +20 -2
  130. package/dist/gate-decision.d.ts.map +1 -1
  131. package/dist/gate-decision.js +30 -3
  132. package/dist/graph-ir.d.ts +25 -0
  133. package/dist/graph-ir.d.ts.map +1 -1
  134. package/dist/graph-walk.d.ts +66 -0
  135. package/dist/graph-walk.d.ts.map +1 -0
  136. package/dist/graph-walk.js +70 -0
  137. package/dist/import/parser.d.ts +19 -0
  138. package/dist/import/parser.d.ts.map +1 -1
  139. package/dist/lexicon.d.ts +94 -6
  140. package/dist/lexicon.d.ts.map +1 -1
  141. package/dist/lifecycle/gate-ledger.d.ts +11 -0
  142. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  143. package/dist/lifecycle/git.d.ts +2 -5
  144. package/dist/lifecycle/git.d.ts.map +1 -1
  145. package/dist/lifecycle/ledger-read.d.ts +65 -0
  146. package/dist/lifecycle/ledger-read.d.ts.map +1 -0
  147. package/dist/lifecycle/ledger-read.js +160 -0
  148. package/dist/lifecycle/release-workstation.d.ts +7 -0
  149. package/dist/lifecycle/release-workstation.d.ts.map +1 -1
  150. package/dist/lifecycle/release-workstation.js +25 -0
  151. package/dist/lifecycle/run-ledger.d.ts +4 -3
  152. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  153. package/dist/lifecycle/run-ledger.js +1 -0
  154. package/dist/lint/discover.js +3 -1
  155. package/dist/lint/post-synth.d.ts +127 -1
  156. package/dist/lint/post-synth.d.ts.map +1 -1
  157. package/dist/lint/post-synth.js +84 -2
  158. package/dist/lint/rules/op/index.d.ts +1 -0
  159. package/dist/lint/rules/op/index.d.ts.map +1 -1
  160. package/dist/lint/rules/op/index.js +2 -0
  161. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
  162. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
  163. package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
  164. package/dist/op/activities/activity-contracts.d.ts +9 -6
  165. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  166. package/dist/op/activities/activity-contracts.js +5 -2
  167. package/dist/op/activities/index.d.ts +2 -2
  168. package/dist/op/activities/index.d.ts.map +1 -1
  169. package/dist/op/activities/index.js +1 -1
  170. package/dist/op/activities/review-agent.d.ts +90 -4
  171. package/dist/op/activities/review-agent.d.ts.map +1 -1
  172. package/dist/op/activities/review-agent.js +217 -19
  173. package/dist/op/activities/work-evidence.d.ts +8 -1
  174. package/dist/op/activities/work-evidence.d.ts.map +1 -1
  175. package/dist/op/activities/work-evidence.js +1 -0
  176. package/dist/op/builders.d.ts +9 -5
  177. package/dist/op/builders.d.ts.map +1 -1
  178. package/dist/op/builders.js +9 -5
  179. package/dist/op/composites/apply-op.d.ts +29 -2
  180. package/dist/op/composites/apply-op.d.ts.map +1 -1
  181. package/dist/op/composites/apply-op.js +20 -1
  182. package/dist/op/gate-approval-base.d.ts +25 -0
  183. package/dist/op/gate-approval-base.d.ts.map +1 -0
  184. package/dist/op/gate-approval-base.js +71 -0
  185. package/dist/op/gate-approval.d.ts +18 -9
  186. package/dist/op/gate-approval.d.ts.map +1 -1
  187. package/dist/op/gate-approval.js +13 -31
  188. package/dist/op/gate-name.d.ts +13 -0
  189. package/dist/op/gate-name.d.ts.map +1 -1
  190. package/dist/op/gate-name.js +14 -1
  191. package/dist/op/gate-quorum.d.ts +100 -3
  192. package/dist/op/gate-quorum.d.ts.map +1 -1
  193. package/dist/op/gate-quorum.js +108 -6
  194. package/dist/op/gate-review.d.ts +9 -0
  195. package/dist/op/gate-review.d.ts.map +1 -1
  196. package/dist/op/gate-review.js +13 -0
  197. package/dist/op/gate.d.ts +47 -5
  198. package/dist/op/gate.d.ts.map +1 -1
  199. package/dist/op/gate.js +25 -4
  200. package/dist/op/index.d.ts +3 -3
  201. package/dist/op/index.d.ts.map +1 -1
  202. package/dist/op/index.js +1 -1
  203. package/dist/op/local-executor.d.ts +45 -3
  204. package/dist/op/local-executor.d.ts.map +1 -1
  205. package/dist/op/local-executor.js +68 -7
  206. package/dist/op/op-waves-run.d.ts +26 -1
  207. package/dist/op/op-waves-run.d.ts.map +1 -1
  208. package/dist/op/op-waves-run.js +259 -13
  209. package/dist/op/op-waves.d.ts +72 -5
  210. package/dist/op/op-waves.d.ts.map +1 -1
  211. package/dist/op/op-waves.js +110 -15
  212. package/dist/op/runtime.d.ts +11 -0
  213. package/dist/op/runtime.d.ts.map +1 -1
  214. package/dist/op/schedule-overdue.d.ts +16 -0
  215. package/dist/op/schedule-overdue.d.ts.map +1 -1
  216. package/dist/op/schedule-overdue.js +36 -0
  217. package/dist/op/stage-hooks.d.ts +9 -2
  218. package/dist/op/stage-hooks.d.ts.map +1 -1
  219. package/dist/op/stage-hooks.js +7 -5
  220. package/dist/op/workspace-run-env.d.ts +22 -0
  221. package/dist/op/workspace-run-env.d.ts.map +1 -0
  222. package/dist/op/workspace-run-env.js +21 -0
  223. package/dist/pr-forge.d.ts +24 -0
  224. package/dist/pr-forge.d.ts.map +1 -1
  225. package/dist/pr-forge.js +47 -0
  226. package/dist/pr-loop.d.ts +10 -0
  227. package/dist/pr-loop.d.ts.map +1 -1
  228. package/dist/pr-loop.js +4 -0
  229. package/dist/pr-migrations.d.ts +62 -0
  230. package/dist/pr-migrations.d.ts.map +1 -0
  231. package/dist/pr-migrations.js +50 -0
  232. package/dist/pr-post.d.ts +42 -0
  233. package/dist/pr-post.d.ts.map +1 -1
  234. package/dist/pr-post.js +68 -2
  235. package/dist/read-filter.d.ts +65 -0
  236. package/dist/read-filter.d.ts.map +1 -0
  237. package/dist/read-filter.js +179 -0
  238. package/dist/run-event.d.ts +1 -1
  239. package/dist/run-event.d.ts.map +1 -1
  240. package/dist/terraform/carve.d.ts +24 -3
  241. package/dist/terraform/carve.d.ts.map +1 -1
  242. package/dist/terraform/carve.js +32 -14
  243. package/dist/terraform/cuts.d.ts +97 -0
  244. package/dist/terraform/cuts.d.ts.map +1 -0
  245. package/dist/terraform/cuts.js +138 -0
  246. package/dist/terraform/folds.d.ts +26 -0
  247. package/dist/terraform/folds.d.ts.map +1 -0
  248. package/dist/terraform/folds.js +38 -0
  249. package/dist/terraform/graph.d.ts.map +1 -1
  250. package/dist/terraform/graph.js +10 -0
  251. package/dist/terraform/reach.d.ts +140 -0
  252. package/dist/terraform/reach.d.ts.map +1 -0
  253. package/dist/terraform/reach.js +170 -0
  254. package/dist/terraform/score.d.ts +14 -0
  255. package/dist/terraform/score.d.ts.map +1 -1
  256. package/dist/terraform/score.js +14 -31
  257. package/dist/terraform/state.d.ts.map +1 -1
  258. package/dist/terraform/state.js +4 -2
  259. package/dist/terraform/types.d.ts +8 -0
  260. package/dist/terraform/types.d.ts.map +1 -1
  261. package/dist/wave-blocked.d.ts +10 -1
  262. package/dist/wave-blocked.d.ts.map +1 -1
  263. package/dist/wave-blocked.js +31 -3
  264. package/dist/wave-runtime.mjs +264 -254
  265. package/dist/workspace/check.schema.json +13 -2
  266. package/dist/workspace/checks/ci-branch.d.ts +44 -0
  267. package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
  268. package/dist/workspace/checks/ci-branch.js +133 -0
  269. package/dist/workspace/checks/gate-approval.d.ts +58 -0
  270. package/dist/workspace/checks/gate-approval.d.ts.map +1 -0
  271. package/dist/workspace/checks/gate-approval.js +103 -0
  272. package/dist/workspace/checks/links.d.ts +3 -0
  273. package/dist/workspace/checks/links.d.ts.map +1 -1
  274. package/dist/workspace/checks/links.js +12 -1
  275. package/dist/workspace/checks/org.d.ts +48 -0
  276. package/dist/workspace/checks/org.d.ts.map +1 -0
  277. package/dist/workspace/checks/org.js +156 -0
  278. package/dist/workspace/checks.d.ts +12 -1
  279. package/dist/workspace/checks.d.ts.map +1 -1
  280. package/dist/workspace/checks.js +63 -7
  281. package/dist/workspace/ci-branches.d.ts +16 -0
  282. package/dist/workspace/ci-branches.d.ts.map +1 -1
  283. package/dist/workspace/ci-branches.js +27 -0
  284. package/dist/workspace/ci-green-cli.js +7 -7
  285. package/dist/workspace/ci-green-workflow.d.ts +1 -1
  286. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  287. package/dist/workspace/ci-green-workflow.js +13 -8
  288. package/dist/workspace/ci-green.d.ts +14 -0
  289. package/dist/workspace/ci-green.d.ts.map +1 -1
  290. package/dist/workspace/ci-green.js +44 -11
  291. package/dist/workspace/ci-run.d.ts +9 -1
  292. package/dist/workspace/ci-run.d.ts.map +1 -1
  293. package/dist/workspace/ci-run.js +67 -6
  294. package/dist/workspace/ci-serve.d.ts +9 -0
  295. package/dist/workspace/ci-serve.d.ts.map +1 -1
  296. package/dist/workspace/ci-serve.js +114 -76
  297. package/dist/workspace/compose-graph.d.ts +14 -0
  298. package/dist/workspace/compose-graph.d.ts.map +1 -1
  299. package/dist/workspace/composites.d.ts +4 -1
  300. package/dist/workspace/composites.d.ts.map +1 -1
  301. package/dist/workspace/composites.js +5 -2
  302. package/dist/workspace/composites.schema.json +22 -0
  303. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  304. package/dist/workspace/cross-root-lint.d.ts +56 -0
  305. package/dist/workspace/cross-root-lint.d.ts.map +1 -0
  306. package/dist/workspace/cross-root-lint.js +111 -0
  307. package/dist/workspace/dbt-models-reader.d.ts +44 -0
  308. package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
  309. package/dist/workspace/dbt-models-reader.js +356 -0
  310. package/dist/workspace/dbt-project-common.d.ts +40 -0
  311. package/dist/workspace/dbt-project-common.d.ts.map +1 -0
  312. package/dist/workspace/dbt-project-common.js +106 -0
  313. package/dist/workspace/dbt-project-reader.d.ts +4 -3
  314. package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
  315. package/dist/workspace/dbt-project-reader.js +14 -108
  316. package/dist/workspace/decision-rank.d.ts +26 -0
  317. package/dist/workspace/decision-rank.d.ts.map +1 -0
  318. package/dist/workspace/decision-rank.js +16 -0
  319. package/dist/workspace/declaration.d.ts +50 -2
  320. package/dist/workspace/declaration.d.ts.map +1 -1
  321. package/dist/workspace/declaration.js +57 -11
  322. package/dist/workspace/declaration.schema.json +166 -24
  323. package/dist/workspace/environments.d.ts +26 -2
  324. package/dist/workspace/environments.d.ts.map +1 -1
  325. package/dist/workspace/environments.js +26 -2
  326. package/dist/workspace/exit-cli.d.ts +44 -0
  327. package/dist/workspace/exit-cli.d.ts.map +1 -0
  328. package/dist/workspace/exit-cli.js +198 -0
  329. package/dist/workspace/exit.d.ts +159 -0
  330. package/dist/workspace/exit.d.ts.map +1 -0
  331. package/dist/workspace/exit.js +480 -0
  332. package/dist/workspace/exit.schema.json +257 -0
  333. package/dist/workspace/flyway-dir.d.ts +39 -0
  334. package/dist/workspace/flyway-dir.d.ts.map +1 -0
  335. package/dist/workspace/flyway-dir.js +104 -0
  336. package/dist/workspace/flyway-layout.d.ts +96 -0
  337. package/dist/workspace/flyway-layout.d.ts.map +1 -0
  338. package/dist/workspace/flyway-layout.js +308 -0
  339. package/dist/workspace/graph-cache.d.ts +3 -1
  340. package/dist/workspace/graph-cache.d.ts.map +1 -1
  341. package/dist/workspace/graph-cache.js +12 -1
  342. package/dist/workspace/graph-cli.d.ts.map +1 -1
  343. package/dist/workspace/graph-cli.js +26 -8
  344. package/dist/workspace/graph.schema.json +34 -0
  345. package/dist/workspace/import-fetch.d.ts +38 -0
  346. package/dist/workspace/import-fetch.d.ts.map +1 -0
  347. package/dist/workspace/import-fetch.js +99 -0
  348. package/dist/workspace/import-init.d.ts +26 -0
  349. package/dist/workspace/import-init.d.ts.map +1 -0
  350. package/dist/workspace/import-init.js +130 -0
  351. package/dist/workspace/import-source.d.ts +147 -0
  352. package/dist/workspace/import-source.d.ts.map +1 -0
  353. package/dist/workspace/import-source.js +370 -0
  354. package/dist/workspace/init.d.ts +4 -0
  355. package/dist/workspace/init.d.ts.map +1 -1
  356. package/dist/workspace/init.js +70 -3
  357. package/dist/workspace/intent.d.ts +2 -3
  358. package/dist/workspace/intent.d.ts.map +1 -1
  359. package/dist/workspace/intent.js +5 -9
  360. package/dist/workspace/kind-link-readers.d.ts +59 -2
  361. package/dist/workspace/kind-link-readers.d.ts.map +1 -1
  362. package/dist/workspace/kind-link-readers.js +17 -1
  363. package/dist/workspace/kind-readers.d.ts +6 -0
  364. package/dist/workspace/kind-readers.d.ts.map +1 -1
  365. package/dist/workspace/kind-readers.js +18 -0
  366. package/dist/workspace/kinds.d.ts +90 -3
  367. package/dist/workspace/kinds.d.ts.map +1 -1
  368. package/dist/workspace/kinds.js +161 -7
  369. package/dist/workspace/ledger.schema.json +158 -0
  370. package/dist/workspace/lexicon-outputs.d.ts +20 -1
  371. package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
  372. package/dist/workspace/lexicon-outputs.js +102 -10
  373. package/dist/workspace/lineage-check.d.ts +3 -0
  374. package/dist/workspace/lineage-check.d.ts.map +1 -1
  375. package/dist/workspace/lineage-check.js +23 -4
  376. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  377. package/dist/workspace/lineage-upgrade.js +13 -4
  378. package/dist/workspace/links.d.ts +8 -1
  379. package/dist/workspace/links.d.ts.map +1 -1
  380. package/dist/workspace/links.js +25 -1
  381. package/dist/workspace/ls.d.ts +8 -0
  382. package/dist/workspace/ls.d.ts.map +1 -1
  383. package/dist/workspace/ls.js +7 -1
  384. package/dist/workspace/ls.schema.json +7 -0
  385. package/dist/workspace/member-commands.d.ts.map +1 -1
  386. package/dist/workspace/member-commands.js +54 -5
  387. package/dist/workspace/migrate-links.d.ts +45 -0
  388. package/dist/workspace/migrate-links.d.ts.map +1 -0
  389. package/dist/workspace/migrate-links.js +100 -0
  390. package/dist/workspace/migration-dirs.d.ts +74 -0
  391. package/dist/workspace/migration-dirs.d.ts.map +1 -0
  392. package/dist/workspace/migration-dirs.js +254 -0
  393. package/dist/workspace/new-migrations.d.ts +47 -0
  394. package/dist/workspace/new-migrations.d.ts.map +1 -0
  395. package/dist/workspace/new-migrations.js +91 -0
  396. package/dist/workspace/pr-report.schema.json +46 -0
  397. package/dist/workspace/promote.d.ts +96 -0
  398. package/dist/workspace/promote.d.ts.map +1 -0
  399. package/dist/workspace/promote.js +263 -0
  400. package/dist/workspace/reason-codes.d.ts +21 -0
  401. package/dist/workspace/reason-codes.d.ts.map +1 -1
  402. package/dist/workspace/reason-codes.js +25 -0
  403. package/dist/workspace/records-cli.d.ts +11 -0
  404. package/dist/workspace/records-cli.d.ts.map +1 -1
  405. package/dist/workspace/records-cli.js +57 -5
  406. package/dist/workspace/records.schema.json +5 -0
  407. package/dist/workspace/release-candidates.d.ts +2 -0
  408. package/dist/workspace/release-candidates.d.ts.map +1 -1
  409. package/dist/workspace/release-candidates.js +5 -2
  410. package/dist/workspace/release-train.d.ts +53 -0
  411. package/dist/workspace/release-train.d.ts.map +1 -0
  412. package/dist/workspace/release-train.js +134 -0
  413. package/dist/workspace/release-workflow.d.ts.map +1 -1
  414. package/dist/workspace/release-workflow.js +4 -3
  415. package/dist/workspace/repo-ref.d.ts +45 -0
  416. package/dist/workspace/repo-ref.d.ts.map +1 -0
  417. package/dist/workspace/repo-ref.js +114 -0
  418. package/dist/workspace/run-event.schema.json +2 -2
  419. package/dist/workspace/runs-cli.d.ts +9 -0
  420. package/dist/workspace/runs-cli.d.ts.map +1 -1
  421. package/dist/workspace/runs-cli.js +48 -3
  422. package/dist/workspace/runs.schema.json +7 -0
  423. package/dist/workspace/search-contract.d.ts +1 -1
  424. package/dist/workspace/search-contract.d.ts.map +1 -1
  425. package/dist/workspace/status-decisions.d.ts +37 -0
  426. package/dist/workspace/status-decisions.d.ts.map +1 -0
  427. package/dist/workspace/status-decisions.js +147 -0
  428. package/dist/workspace/status-gates.d.ts +9 -0
  429. package/dist/workspace/status-gates.d.ts.map +1 -1
  430. package/dist/workspace/status.d.ts +4 -0
  431. package/dist/workspace/status.d.ts.map +1 -1
  432. package/dist/workspace/status.js +14 -2
  433. package/dist/workspace/status.schema.json +6 -0
  434. package/dist/workspace/work-cli.d.ts.map +1 -1
  435. package/dist/workspace/work-cli.js +5 -3
  436. package/dist/workspace/work-evidence-ops.d.ts +83 -0
  437. package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
  438. package/dist/workspace/work-evidence-ops.js +169 -0
  439. package/dist/workspace/work-evidence.d.ts +12 -2
  440. package/dist/workspace/work-evidence.d.ts.map +1 -1
  441. package/dist/workspace/work-evidence.js +21 -3
  442. package/dist/workspace/work-evidence.schema.json +49 -2
  443. package/dist/workspace/workspace-gates.d.ts +110 -0
  444. package/dist/workspace/workspace-gates.d.ts.map +1 -0
  445. package/dist/workspace/workspace-gates.js +140 -0
  446. package/dist/workspace/workspace-kinds.schema.json +108 -10
  447. package/dist/workspace/workspace-ledger.d.ts +76 -0
  448. package/dist/workspace/workspace-ledger.d.ts.map +1 -0
  449. package/dist/workspace/workspace-ledger.js +91 -0
  450. package/dist/workspace/workspace-run.d.ts +112 -0
  451. package/dist/workspace/workspace-run.d.ts.map +1 -0
  452. package/dist/workspace/workspace-run.js +432 -0
  453. package/package.json +1 -1
  454. package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  455. package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  456. package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  457. package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  458. package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  459. package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  460. package/src/__golden__/wave-loop/op-cost-member.json +13 -0
  461. package/src/__golden__/wave-loop/op-denied-source.json +16 -0
  462. package/src/__golden__/wave-loop/op-denied.json +16 -0
  463. package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  464. package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  465. package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
  466. package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
  467. package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
  468. package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  469. package/src/__golden__/wave-loop/op-held-on.json +16 -0
  470. package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  471. package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  472. package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  473. package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  474. package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
  475. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  476. package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
  477. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  478. package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
  479. package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
  480. package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  481. package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  482. package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
  483. package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  484. package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  485. package/src/__golden__/wave-loop/op-overridden.json +16 -0
  486. package/src/__golden__/wave-loop/op-per-run.json +20 -0
  487. package/src/__golden__/wave-loop/op-point.json +13 -0
  488. package/src/__golden__/wave-loop/op-pr-review.json +10 -0
  489. package/src/__golden__/wave-loop/op-sealed.json +10 -0
  490. package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
  491. package/src/__golden__/wave-loop/op-spend.json +10 -0
  492. package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
  493. package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
  494. package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
  495. package/src/__golden__/wave-loop/op-wide.json +39 -0
  496. package/src/action-pins.test.ts +78 -0
  497. package/src/action-pins.ts +92 -0
  498. package/src/cli/commands/carve-score-once.test.ts +99 -0
  499. package/src/cli/commands/carve.ts +37 -6
  500. package/src/cli/commands/check-lexicon.test.ts +2 -2
  501. package/src/cli/commands/import-report.ts +71 -0
  502. package/src/cli/commands/import.ts +33 -3
  503. package/src/cli/commands/migrate-dialect.test.ts +145 -0
  504. package/src/cli/commands/migrate.ts +132 -11
  505. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
  506. package/src/cli/handlers/approve.ts +33 -4
  507. package/src/cli/handlers/build.ts +7 -1
  508. package/src/cli/handlers/graph-reader.test.ts +31 -0
  509. package/src/cli/handlers/graph-reader.ts +51 -0
  510. package/src/cli/handlers/graph.ts +14 -0
  511. package/src/cli/handlers/ledger.ts +54 -0
  512. package/src/cli/handlers/migrate.ts +21 -12
  513. package/src/cli/handlers/misc.ts +10 -1
  514. package/src/cli/handlers/pr-env.ts +2 -2
  515. package/src/cli/handlers/pr.ts +176 -37
  516. package/src/cli/handlers/query.ts +16 -4
  517. package/src/cli/handlers/release.ts +6 -3
  518. package/src/cli/handlers/search.ts +13 -34
  519. package/src/cli/main.test.ts +17 -0
  520. package/src/cli/main.ts +87 -14
  521. package/src/cli/parse-args.ts +35 -2
  522. package/src/cli/registry.ts +22 -4
  523. package/src/components/builders.ts +8 -0
  524. package/src/components/capability-plugin-loader.ts +13 -1
  525. package/src/components/capability-plugin.ts +16 -0
  526. package/src/components/pr-env-run-bundle.test.ts +1 -0
  527. package/src/components/pr-hooks.test.ts +73 -5
  528. package/src/components/pr-hooks.ts +95 -5
  529. package/src/components/pr-pipeline.ts +64 -16
  530. package/src/components/pr-policy.test.ts +64 -1
  531. package/src/components/pr-policy.ts +53 -0
  532. package/src/components/pr-run.ts +4 -0
  533. package/src/components/registry.test.ts +1 -1
  534. package/src/components/starter-plugin.ts +6 -0
  535. package/src/components/verbs/index.ts +2 -0
  536. package/src/components/verbs/publish-artifact.test.ts +60 -0
  537. package/src/components/verbs/publish-artifact.ts +122 -0
  538. package/src/components/verbs/publish-image.test.ts +101 -0
  539. package/src/components/verbs/publish-image.ts +265 -0
  540. package/src/config-environments.ts +28 -1
  541. package/src/config.ts +24 -1
  542. package/src/gate-decision-bundle.test.ts +5 -2
  543. package/src/gate-decision-identity.test.ts +20 -0
  544. package/src/gate-decision.ts +46 -3
  545. package/src/graph-ir.ts +27 -0
  546. package/src/graph-walk.test.ts +44 -0
  547. package/src/graph-walk.ts +102 -0
  548. package/src/import/parser.ts +20 -0
  549. package/src/lexicon.ts +98 -6
  550. package/src/lifecycle/gate-ledger.ts +8 -0
  551. package/src/lifecycle/git.ts +2 -2
  552. package/src/lifecycle/ledger-read.test.ts +107 -0
  553. package/src/lifecycle/ledger-read.ts +195 -0
  554. package/src/lifecycle/release-workstation.ts +24 -0
  555. package/src/lifecycle/run-ledger.ts +5 -2
  556. package/src/lint/cross-root.test.ts +122 -0
  557. package/src/lint/discover.ts +3 -1
  558. package/src/lint/post-synth.ts +212 -2
  559. package/src/lint/rules/op/index.ts +2 -0
  560. package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
  561. package/src/op/activities/activity-contracts.ts +5 -2
  562. package/src/op/activities/index.ts +2 -2
  563. package/src/op/activities/review-agent.test.ts +221 -4
  564. package/src/op/activities/review-agent.ts +265 -22
  565. package/src/op/activities/work-evidence.ts +9 -1
  566. package/src/op/builders.ts +9 -5
  567. package/src/op/composites/apply-op.test.ts +108 -0
  568. package/src/op/composites/apply-op.ts +37 -2
  569. package/src/op/gate-approval-at-base.test.ts +97 -0
  570. package/src/op/gate-approval-base.ts +72 -0
  571. package/src/op/gate-approval.test.ts +6 -1
  572. package/src/op/gate-approval.ts +22 -27
  573. package/src/op/gate-name.ts +16 -1
  574. package/src/op/gate-quorum-at-base.test.ts +211 -0
  575. package/src/op/gate-quorum.ts +156 -8
  576. package/src/op/gate-review.ts +14 -0
  577. package/src/op/gate.ts +69 -9
  578. package/src/op/index.ts +3 -2
  579. package/src/op/local-executor.ts +91 -11
  580. package/src/op/op-waves-depends-on.test.ts +81 -0
  581. package/src/op/op-waves-hook-targets.test.ts +4 -2
  582. package/src/op/op-waves-quorum-product.test.ts +158 -0
  583. package/src/op/op-waves-run.ts +288 -14
  584. package/src/op/op-waves.ts +143 -14
  585. package/src/op/runtime.ts +8 -0
  586. package/src/op/schedule-overdue.ts +33 -0
  587. package/src/op/stage-hooks.ts +13 -5
  588. package/src/op/workspace-run-env.ts +29 -0
  589. package/src/pr-forge-bundle.test.ts +2 -2
  590. package/src/pr-forge.test.ts +47 -0
  591. package/src/pr-forge.ts +68 -0
  592. package/src/pr-loop-bundle.test.ts +3 -2
  593. package/src/pr-loop.test.ts +52 -0
  594. package/src/pr-loop.ts +12 -0
  595. package/src/pr-migrations.ts +97 -0
  596. package/src/pr-post-open.e2e.test.ts +36 -0
  597. package/src/pr-post.test.ts +102 -3
  598. package/src/pr-post.ts +90 -2
  599. package/src/read-filter.test.ts +54 -0
  600. package/src/read-filter.ts +193 -0
  601. package/src/run-event.ts +1 -1
  602. package/src/stage-hooks-bundle.test.ts +1 -0
  603. package/src/terraform/carve.ts +52 -15
  604. package/src/terraform/cuts.test.ts +173 -0
  605. package/src/terraform/cuts.ts +188 -0
  606. package/src/terraform/folds.ts +44 -0
  607. package/src/terraform/graph.ts +9 -0
  608. package/src/terraform/radius.test.ts +90 -0
  609. package/src/terraform/reach.test.ts +146 -0
  610. package/src/terraform/reach.ts +247 -0
  611. package/src/terraform/score.ts +27 -29
  612. package/src/terraform/state.ts +3 -1
  613. package/src/terraform/types.ts +8 -0
  614. package/src/wave-blocked.ts +28 -3
  615. package/src/wave-loop-golden.test.ts +4 -3
  616. package/src/workspace/__fixtures__/import/spacelift.json +9 -0
  617. package/src/workspace/__fixtures__/import/tfc.json +13 -0
  618. package/src/workspace/behold-kinds.test.ts +2 -1
  619. package/src/workspace/check-contract.test.ts +8 -6
  620. package/src/workspace/check.schema.json +13 -2
  621. package/src/workspace/checks/ci-branch.test.ts +128 -0
  622. package/src/workspace/checks/ci-branch.ts +141 -0
  623. package/src/workspace/checks/gate-approval.test.ts +77 -0
  624. package/src/workspace/checks/gate-approval.ts +121 -0
  625. package/src/workspace/checks/links.ts +12 -1
  626. package/src/workspace/checks/org.ts +169 -0
  627. package/src/workspace/checks.ts +68 -7
  628. package/src/workspace/ci-branches.ts +31 -0
  629. package/src/workspace/ci-green-branches.test.ts +153 -0
  630. package/src/workspace/ci-green-cli.ts +7 -7
  631. package/src/workspace/ci-green-workflow.test.ts +4 -3
  632. package/src/workspace/ci-green-workflow.ts +13 -9
  633. package/src/workspace/ci-green.ts +49 -12
  634. package/src/workspace/ci-run.test.ts +72 -1
  635. package/src/workspace/ci-run.ts +68 -8
  636. package/src/workspace/ci-serve.test.ts +95 -0
  637. package/src/workspace/ci-serve.ts +106 -65
  638. package/src/workspace/component-environments.test.ts +25 -2
  639. package/src/workspace/compose-graph.ts +9 -2
  640. package/src/workspace/composites.schema.json +22 -0
  641. package/src/workspace/composites.test.ts +39 -8
  642. package/src/workspace/composites.ts +6 -2
  643. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  644. package/src/workspace/cross-root-lint.test.ts +90 -0
  645. package/src/workspace/cross-root-lint.ts +126 -0
  646. package/src/workspace/dbt-kind.test.ts +3 -2
  647. package/src/workspace/dbt-models-reader.ts +362 -0
  648. package/src/workspace/dbt-models.test.ts +248 -0
  649. package/src/workspace/dbt-project-common.ts +113 -0
  650. package/src/workspace/dbt-project-reader.ts +29 -112
  651. package/src/workspace/decision-rank.ts +40 -0
  652. package/src/workspace/declaration.schema.json +166 -24
  653. package/src/workspace/declaration.ts +101 -15
  654. package/src/workspace/environments.ts +40 -4
  655. package/src/workspace/exit-cli.ts +219 -0
  656. package/src/workspace/exit.schema.json +257 -0
  657. package/src/workspace/exit.test.ts +185 -0
  658. package/src/workspace/exit.ts +560 -0
  659. package/src/workspace/flyway-dir.ts +113 -0
  660. package/src/workspace/flyway-layout.ts +337 -0
  661. package/src/workspace/graph-cache.ts +12 -2
  662. package/src/workspace/graph-cli.ts +25 -10
  663. package/src/workspace/graph-contract.test.ts +4 -2
  664. package/src/workspace/graph.schema.json +34 -0
  665. package/src/workspace/identity.test.ts +29 -2
  666. package/src/workspace/import-fetch.ts +151 -0
  667. package/src/workspace/import-init.ts +147 -0
  668. package/src/workspace/import-source.test.ts +183 -0
  669. package/src/workspace/import-source.ts +462 -0
  670. package/src/workspace/init.test.ts +55 -1
  671. package/src/workspace/init.ts +66 -4
  672. package/src/workspace/intent.ts +6 -14
  673. package/src/workspace/kind-link-readers.ts +68 -3
  674. package/src/workspace/kind-readers.test.ts +36 -0
  675. package/src/workspace/kind-readers.ts +18 -1
  676. package/src/workspace/kinds.test.ts +35 -7
  677. package/src/workspace/kinds.ts +212 -11
  678. package/src/workspace/ledger.schema.json +158 -0
  679. package/src/workspace/lexicon-outputs.ts +132 -13
  680. package/src/workspace/lineage-check.ts +30 -7
  681. package/src/workspace/lineage-upgrade.test.ts +20 -0
  682. package/src/workspace/lineage-upgrade.ts +16 -7
  683. package/src/workspace/links.ts +24 -1
  684. package/src/workspace/ls-contract.test.ts +1 -1
  685. package/src/workspace/ls.schema.json +7 -0
  686. package/src/workspace/ls.ts +10 -1
  687. package/src/workspace/member-commands.ts +60 -2
  688. package/src/workspace/member-file-schema.test.ts +124 -0
  689. package/src/workspace/migrate-links.test.ts +99 -0
  690. package/src/workspace/migrate-links.ts +108 -0
  691. package/src/workspace/migration-dirs.test.ts +272 -0
  692. package/src/workspace/migration-dirs.ts +267 -0
  693. package/src/workspace/new-migrations.test.ts +98 -0
  694. package/src/workspace/new-migrations.ts +109 -0
  695. package/src/workspace/org-scope.test.ts +238 -0
  696. package/src/workspace/pr-report.schema.json +46 -0
  697. package/src/workspace/promote.test.ts +114 -0
  698. package/src/workspace/promote.ts +302 -0
  699. package/src/workspace/reason-codes.test.ts +10 -1
  700. package/src/workspace/reason-codes.ts +25 -0
  701. package/src/workspace/records-cli.ts +58 -5
  702. package/src/workspace/records.schema.json +5 -0
  703. package/src/workspace/release-candidates.ts +7 -2
  704. package/src/workspace/release-train.ts +157 -0
  705. package/src/workspace/release-workflow.ts +4 -3
  706. package/src/workspace/repo-ref.ts +113 -0
  707. package/src/workspace/run-event.schema.json +2 -2
  708. package/src/workspace/runs-cli.ts +48 -4
  709. package/src/workspace/runs.schema.json +7 -0
  710. package/src/workspace/status-contract.test.ts +46 -1
  711. package/src/workspace/status-decisions.test.ts +100 -0
  712. package/src/workspace/status-decisions.ts +151 -0
  713. package/src/workspace/status-gates.ts +9 -0
  714. package/src/workspace/status.schema.json +6 -0
  715. package/src/workspace/status.ts +17 -3
  716. package/src/workspace/where-filters.test.ts +39 -0
  717. package/src/workspace/work-cli.ts +5 -3
  718. package/src/workspace/work-evidence-ops.test.ts +176 -0
  719. package/src/workspace/work-evidence-ops.ts +189 -0
  720. package/src/workspace/work-evidence.schema.json +49 -2
  721. package/src/workspace/work-evidence.ts +22 -3
  722. package/src/workspace/workspace-gates.ts +200 -0
  723. package/src/workspace/workspace-kinds.schema.json +108 -10
  724. package/src/workspace/workspace-ledger.ts +130 -0
  725. package/src/workspace/workspace-run.test.ts +322 -0
  726. package/src/workspace/workspace-run.ts +461 -0
@@ -0,0 +1,60 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { mkdtempSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { createHash } from "node:crypto";
6
+ import {
7
+ createPublishArtifactCapability,
8
+ selectStorageBackend,
9
+ type ArtifactStorageBackend,
10
+ } from "./publish-artifact";
11
+
12
+ function fake(name: string, prefix: string) {
13
+ const uploads: Array<{ from: string; to: string }> = [];
14
+ const backend: ArtifactStorageBackend = {
15
+ name,
16
+ matches: (uri) => uri.startsWith(prefix),
17
+ upload: async (args) => void uploads.push(args),
18
+ };
19
+ return { backend, uploads };
20
+ }
21
+
22
+ describe("selectStorageBackend (#4408)", () => {
23
+ const s3 = fake("s3", "s3://").backend;
24
+ const gcs = fake("gcs", "gs://").backend;
25
+ it("prefers a named backend, then the matching one, then the only one", () => {
26
+ expect(selectStorageBackend([s3, gcs], "s3://b/k", "gcs")).toBe(gcs);
27
+ expect(selectStorageBackend([s3, gcs], "gs://b/k")).toBe(gcs);
28
+ expect(selectStorageBackend([s3], "weird://x")).toBe(s3);
29
+ });
30
+ it("refuses an unknown name, an ambiguous match and an undecidable pick", () => {
31
+ expect(() => selectStorageBackend([s3], "s3://b/k", "nope")).toThrow(/no storage backend "nope"/);
32
+ expect(() => selectStorageBackend([s3, fake("s3b", "s3://").backend], "s3://b/k")).toThrow(/matches several/);
33
+ expect(() => selectStorageBackend([s3, gcs], "weird://x")).toThrow(/cannot tell/);
34
+ });
35
+ });
36
+
37
+ describe("publish-artifact (core)", () => {
38
+ it("uploads through the backend the environment names and returns uri + digest", async () => {
39
+ const dir = mkdtempSync(join(tmpdir(), "chant-pa-core-"));
40
+ const file = join(dir, "app.jar");
41
+ writeFileSync(file, "JAR");
42
+ const a = fake("s3", "s3://");
43
+ const b = fake("gcs", "gs://");
44
+ const cap = createPublishArtifactCapability({ backends: [a.backend, b.backend] });
45
+ const out = await cap.run({ env: "dev", component: "c", vars: { publish: { backend: "gcs" } } } as never, { from: file, to: "gs://bk/jars/" });
46
+ expect(out).toEqual({ uri: "gs://bk/jars/app.jar", digest: `sha256:${createHash("sha256").update("JAR").digest("hex")}` });
47
+ expect(b.uploads).toEqual([{ from: file, to: "gs://bk/jars/app.jar" }]);
48
+ expect(a.uploads).toEqual([]);
49
+ });
50
+
51
+ it("withStorageBackends rebinds the verb", async () => {
52
+ const dir = mkdtempSync(join(tmpdir(), "chant-pa-core-"));
53
+ const file = join(dir, "x.zip");
54
+ writeFileSync(file, "Z");
55
+ const a = fake("s3", "s3://");
56
+ const cap = createPublishArtifactCapability().withStorageBackends([a.backend]) as ReturnType<typeof createPublishArtifactCapability>;
57
+ await cap.run({ env: "dev", component: "c" } as never, { from: file, to: "s3://b/x.zip" });
58
+ expect(a.uploads).toHaveLength(1);
59
+ });
60
+ });
@@ -0,0 +1,122 @@
1
+ /**
2
+ * `publish-artifact` (aliased `publish-asset`): the object-storage half of the
3
+ * publish family, owned by core (#4408), the same way `publish-image` is.
4
+ * "Put this archive file at the deploy target" is one agnostic verb; what
5
+ * differs per cloud is the upload. Each cloud lexicon contributes that as an
6
+ * {@link ArtifactStorageBackend} through `CapabilityPlugin.storageBackends()`
7
+ * (aws: S3, azure: Blob, gcp: GCS), and the environment picks one (see
8
+ * {@link selectStorageBackend}).
9
+ *
10
+ * This is a sibling of `registryBackends()` rather than a reuse of it: a
11
+ * registry backend only logs a docker client in, while a storage backend
12
+ * moves bytes, and the two are matched against different destinations.
13
+ *
14
+ * Core computes the content digest and the final object URI; the backend only
15
+ * uploads. No rollback.
16
+ */
17
+
18
+ import { createHash } from "node:crypto";
19
+ import { readFileSync } from "node:fs";
20
+ import type { Capability } from "../capability";
21
+ import { archiveRelativePath } from "./build-archive";
22
+
23
+ export interface PublishArtifactInput {
24
+ /** Path of the artifact inside the build archive (e.g. a jar or zip). */
25
+ from: string;
26
+ /** Destination (e.g. `$env.s3` resolved by the orchestrator). A trailing `/` appends the file's basename. */
27
+ to: string;
28
+ /** Which storage backend to use ("s3", "azure-blob", "gcs"). Normally left out: `$env.publish.backend` or the destination URI picks it. */
29
+ backend?: string;
30
+ }
31
+
32
+ export interface PublishArtifactOutput {
33
+ /** Location the artifact was published to, referenced downstream as `@<component>.publish.uri`. */
34
+ uri: string;
35
+ /** Content hash of the published artifact. */
36
+ digest: string;
37
+ }
38
+
39
+ /** A cloud's way of putting one local file at a storage URI. */
40
+ export interface ArtifactStorageBackend {
41
+ /** Stable name an environment selects by: "s3", "azure-blob", "gcs". */
42
+ readonly name: string;
43
+ /** True when this backend owns the destination URI. */
44
+ matches(uri: string): boolean;
45
+ /** Upload the local file `from` to the full object URI `to`. */
46
+ upload(args: { from: string; to: string }): Promise<void>;
47
+ }
48
+
49
+ export class StorageBackendError extends Error {
50
+ constructor(message: string) {
51
+ super(message);
52
+ this.name = "StorageBackendError";
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Pick the storage backend for one publish. Order: the step's `backend`, then
58
+ * the environment's `ctx.vars.publish.backend`, then the single backend whose
59
+ * `matches` claims the destination, then the only backend there is.
60
+ */
61
+ export function selectStorageBackend(
62
+ backends: readonly ArtifactStorageBackend[],
63
+ uri: string,
64
+ named?: string,
65
+ ): ArtifactStorageBackend {
66
+ const names = backends.map((b) => b.name).sort().join(", ") || "none";
67
+ if (named) {
68
+ const found = backends.find((b) => b.name === named);
69
+ if (!found) {
70
+ throw new StorageBackendError(
71
+ `publish-artifact: no storage backend "${named}" is registered (available: ${names}); declare the lexicon that contributes it in chant.config.ts`,
72
+ );
73
+ }
74
+ return found;
75
+ }
76
+ const matched = backends.filter((b) => b.matches(uri));
77
+ if (matched.length === 1) return matched[0]!;
78
+ if (matched.length > 1) {
79
+ throw new StorageBackendError(
80
+ `publish-artifact: destination "${uri}" matches several backends (${matched.map((b) => b.name).join(", ")}); set $env.publish.backend`,
81
+ );
82
+ }
83
+ if (backends.length === 1) return backends[0]!;
84
+ throw new StorageBackendError(
85
+ `publish-artifact: cannot tell which storage backend serves "${uri}" (available: ${names}); set $env.publish.backend`,
86
+ );
87
+ }
88
+
89
+ /** Implemented by a capability that wants the plugins' storage backends injected when a registry is built. */
90
+ export interface StorageBackendConsumer {
91
+ withStorageBackends(backends: readonly ArtifactStorageBackend[]): Capability<never, unknown>;
92
+ }
93
+
94
+ export function isStorageBackendConsumer(value: unknown): value is StorageBackendConsumer {
95
+ return typeof value === "object" && value !== null && typeof (value as StorageBackendConsumer).withStorageBackends === "function";
96
+ }
97
+
98
+ export type PublishArtifactCapability = Capability<PublishArtifactInput, PublishArtifactOutput> & StorageBackendConsumer;
99
+
100
+ /** Build the `publish-artifact` verb over the storage backends available. */
101
+ export function createPublishArtifactCapability(options: { backends?: readonly ArtifactStorageBackend[] } = {}): PublishArtifactCapability {
102
+ const backends = options.backends ?? [];
103
+ return {
104
+ kind: "publish-artifact",
105
+ withStorageBackends(next) {
106
+ return createPublishArtifactCapability({ backends: next });
107
+ },
108
+ async run(ctx, input) {
109
+ const localPath = archiveRelativePath(input.from);
110
+ const digest = `sha256:${createHash("sha256").update(readFileSync(localPath)).digest("hex")}`;
111
+ const basename = localPath.split("/").pop() ?? localPath;
112
+ const uri = input.to.endsWith("/") ? `${input.to}${basename}` : input.to;
113
+ const envBackend = (ctx.vars?.publish as { backend?: unknown } | undefined)?.backend;
114
+ const backend = selectStorageBackend(backends, uri, input.backend ?? (typeof envBackend === "string" ? envBackend : undefined));
115
+ await backend.upload({ from: localPath, to: uri });
116
+ return { uri, digest };
117
+ },
118
+ };
119
+ }
120
+
121
+ /** The starter plugin's `publish-artifact`: storage backends injected from the loaded plugins. */
122
+ export const publishArtifactCapability: PublishArtifactCapability = createPublishArtifactCapability();
@@ -0,0 +1,101 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ RegistryBackendError,
4
+ createPublishImageCapability,
5
+ selectRegistryBackend,
6
+ type RegistryLoginBackend,
7
+ } from "./publish-image";
8
+ import type { DockerClient } from "./cloud-executor";
9
+ import { CapabilityRegistry } from "../capability";
10
+ import { registerCapabilityPlugins, DuplicateCapabilityKindError } from "../capability-plugin-loader";
11
+ import { starterCapabilityPlugin } from "../starter-plugin";
12
+ import type { CapabilityPlugin } from "../capability-plugin";
13
+
14
+ function backend(name: string, host: RegExp, log: string[] = []): RegistryLoginBackend {
15
+ return { name, matches: (r) => host.test(r), login: async (r) => void log.push(`${name}:${r}`) };
16
+ }
17
+
18
+ function fakeDocker(log: string[] = []): DockerClient {
19
+ return {
20
+ build: async () => ({ digest: "sha256:b" }),
21
+ save: async () => {},
22
+ load: async () => ({ digest: "local:1" }),
23
+ tag: async () => void log.push("tag"),
24
+ push: async () => ({ digest: "sha256:abc" }),
25
+ remoteDigest: async () => "sha256:abc",
26
+ };
27
+ }
28
+
29
+ const ctx = { env: "dev", component: "svc" };
30
+
31
+ describe("selectRegistryBackend", () => {
32
+ const ecr = backend("ecr", /\.ecr\./);
33
+ const acr = backend("acr", /azurecr/);
34
+ it("picks by registry host", () => {
35
+ expect(selectRegistryBackend([ecr, acr], "x.azurecr.io").name).toBe("acr");
36
+ });
37
+ it("an explicit name wins over the host", () => {
38
+ expect(selectRegistryBackend([ecr, acr], "x.azurecr.io", "ecr").name).toBe("ecr");
39
+ });
40
+ it("rejects an unknown name, listing what is available", () => {
41
+ expect(() => selectRegistryBackend([ecr], "h", "acr")).toThrow(/no registry backend "acr".*ecr/);
42
+ });
43
+ it("falls back to the only backend", () => {
44
+ expect(selectRegistryBackend([ecr], "registry.example.com").name).toBe("ecr");
45
+ });
46
+ it("is ambiguous with several backends and no match", () => {
47
+ expect(() => selectRegistryBackend([ecr, acr], "registry.example.com")).toThrow(RegistryBackendError);
48
+ });
49
+ });
50
+
51
+ describe("core publish-image", () => {
52
+ it("logs in through the backend the environment names", async () => {
53
+ const log: string[] = [];
54
+ const cap = createPublishImageCapability({
55
+ docker: fakeDocker(),
56
+ backends: [backend("ecr", /\.ecr\./, log), backend("acr", /azurecr/, log)],
57
+ });
58
+ const out = await cap.run({ ...ctx, vars: { publish: { backend: "acr" } } }, { from: "a.tar", to: "reg.example.com/app/" });
59
+ expect(log).toEqual(["acr:reg.example.com"]);
60
+ expect(out).toMatchObject({ digest: "sha256:abc", uri: "reg.example.com/app@sha256:abc" });
61
+ });
62
+
63
+ it("the registry rebinds the verb to the backends of every loaded plugin", () => {
64
+ const seen: string[][] = [];
65
+ const consumer = {
66
+ kind: "publish-image",
67
+ run: async () => ({}),
68
+ withRegistryBackends(bs: readonly RegistryLoginBackend[]) {
69
+ seen.push(bs.map((b) => b.name));
70
+ return { kind: "publish-image", run: async () => ({}) };
71
+ },
72
+ };
73
+ const plugin = (name: string, b?: RegistryLoginBackend): CapabilityPlugin => ({
74
+ name,
75
+ version: "0",
76
+ capabilities: () => (name === "core" ? ([consumer] as never) : []),
77
+ ...(b ? { registryBackends: () => [b] } : {}),
78
+ });
79
+ registerCapabilityPlugins(new CapabilityRegistry(), [
80
+ plugin("core"),
81
+ plugin("aws", backend("ecr", /\.ecr\./)),
82
+ plugin("azure", backend("acr", /azurecr/)),
83
+ ]);
84
+ expect(seen).toEqual([["ecr", "acr"]]);
85
+ });
86
+
87
+ it("the starter plugin carries the real publish-image verb", () => {
88
+ expect(starterCapabilityPlugin.capabilities().map((c) => c.kind)).toContain("publish-image");
89
+ });
90
+
91
+ it("a plugin that registers publish-image itself still collides", () => {
92
+ const dup: CapabilityPlugin = {
93
+ name: "dup",
94
+ version: "0",
95
+ capabilities: () => [{ kind: "publish-image", run: async () => ({}) }] as never,
96
+ };
97
+ expect(() => registerCapabilityPlugins(new CapabilityRegistry(), [starterCapabilityPlugin, dup])).toThrow(
98
+ DuplicateCapabilityKindError,
99
+ );
100
+ });
101
+ });
@@ -0,0 +1,265 @@
1
+ /**
2
+ * `publish-image`: the registry half of the publish family, owned by core
3
+ * (#3142, #3143). "Make the image available at the deploy target" is one
4
+ * agnostic verb; what differs per cloud is how the local docker client logs in
5
+ * to the registry. Each cloud lexicon contributes that as a
6
+ * {@link RegistryLoginBackend} through `CapabilityPlugin.registryBackends()`
7
+ * (aws: ECR, azure: ACR, gcp: Artifact Registry), and the environment picks
8
+ * one (see {@link selectRegistryBackend}). So a project may declare several
9
+ * clouds without two plugins registering the same verb.
10
+ *
11
+ * Promotion is by digest and unchanged from the aws-only implementation: load
12
+ * the archived tarball, tag for the destination, log in, push, read the digest
13
+ * back from the push. Optional SBOM/BOM referrers attach via `oras`, best
14
+ * effort. No rollback: a pushed, content-addressed image is not something to
15
+ * undo. See docs/components/build-archive.mdx.
16
+ */
17
+
18
+ import type { Capability } from "../capability";
19
+ import { archiveRelativePath } from "./build-archive";
20
+ import { realDocker, type DockerClient } from "./cloud-executor";
21
+ import { defaultProcessRunner, q, type ProcessRunner } from "./process-runner";
22
+
23
+ /**
24
+ * Common input shape both image-publish backends accept (`publish-image` here, `load-image-on-host` in the aws lexicon): promote the image
25
+ * bytes at archive path `from` to wherever the deploy target can consume
26
+ * them. `to` (registry) and `host` (bare host) are each meaningful to only
27
+ * one backend, so a component authors one step shape (see
28
+ * docs/components/build-archive.mdx) and the env-selected backend (see
29
+ * `selectPublishBackend` below) reads whichever field it needs — the
30
+ * unselected backend's field is simply unused, never a pipeline fork.
31
+ */
32
+ export interface PublishImageInput {
33
+ /** Path of the image tarball inside the build archive (as produced by `docker-build`; an `archive:`-prefixed reference is accepted and stripped). */
34
+ from: string;
35
+ /** Destination registry — required by `publish-image`, ignored by `load-image-on-host` (e.g. `$env.registry` resolved by the orchestrator). */
36
+ to?: string;
37
+ /** Target host — required by `load-image-on-host`, ignored by `publish-image` (SSM instance id, hostname, or host group). */
38
+ host?: string;
39
+ /** Destination path for the tarball on the host, used only by `load-image-on-host`. Default: `/tmp/chant-archive/<basename of from>`. */
40
+ hostPath?: string;
41
+ /**
42
+ * Which registry login backend to use ("ecr", "acr", "artifact-registry").
43
+ * Normally left out: the environment config (`ctx.vars.publish.backend`) or
44
+ * the registry host picks it.
45
+ */
46
+ backend?: string;
47
+ /** Additional tags to apply alongside the digest, used only by `publish-image`. */
48
+ tags?: string[];
49
+ /**
50
+ * #610: the artifact's software SBOM (typically wired from a prior
51
+ * `generate-sbom` step's `sbom` output — see ./sbom.ts's
52
+ * `GenerateSbomOutput`) to attach as an OCI referrer on the pushed image
53
+ * digest, via `oras attach`. Used only by `publish-image`; ignored by
54
+ * `load-image-on-host` (registry-less — nothing to attach to). Omit to
55
+ * skip the attach step entirely (the archive-carried copy remains the
56
+ * SBOM's home either way).
57
+ */
58
+ sbom?: { bytes: string; mediaType: string };
59
+ /**
60
+ * #610: the component-level aggregate BOM (wired from
61
+ * ./component-bom.ts's `aggregateComponentBom` output), attached as a
62
+ * second OCI referrer alongside `sbom` when supplied. Optional and
63
+ * independent of `sbom` — a caller may attach either, both, or neither.
64
+ */
65
+ componentBom?: { bytes: string; mediaType: string };
66
+ }
67
+
68
+ /** Result of `publish-image`'s best-effort OCI-referrer attach step (#610) — always present when `input.sbom`/`input.componentBom` was supplied, `undefined` when neither was (nothing to attach, attach step skipped entirely). */
69
+ export interface ReferrerAttachResult {
70
+ /** True once every requested attach (`sbom`, `componentBom`) succeeded. `false` if `oras` was unavailable or any `oras attach` invocation failed. */
71
+ attached: boolean;
72
+ /** Human-readable reason `attached` is `false` (e.g. "oras is not installed"), omitted when `attached` is `true`. */
73
+ reason?: string;
74
+ }
75
+
76
+ export interface PublishImageOutput {
77
+ /** Content-addressed digest of the promoted image (`sha256:...`) — what the apply step references. */
78
+ digest: string;
79
+ /** Image reference the apply step can pull/run: `registry/repo@sha256:...` for `publish-image`, a host-local reference for `load-image-on-host`. */
80
+ uri: string;
81
+ /** #610: outcome of the best-effort SBOM/component-BOM referrer attach, when `input.sbom`/`input.componentBom` was supplied. `undefined` for `load-image-on-host` (no attach concept — registry-less) and for `publish-image` calls that supplied neither. */
82
+ referrerAttach?: ReferrerAttachResult;
83
+ }
84
+
85
+ /**
86
+ * Common shape both image-publish backends satisfy: promote the image bytes
87
+ * held in the build archive to wherever the deploy target can consume them,
88
+ * and return the identity the apply step references. The backend is
89
+ * selected per environment (`selectPublishBackend`), never per component.
90
+ */
91
+ export type PublishImageBackend = Capability<PublishImageInput, PublishImageOutput>;
92
+
93
+ // ── registry login backends ──────────────────────────────────────────────────
94
+
95
+ /** A cloud's way of authenticating the local docker client to its registry. */
96
+ export interface RegistryLoginBackend {
97
+ /** Stable name an environment selects by: "ecr", "acr", "artifact-registry". */
98
+ readonly name: string;
99
+ /** True when this backend owns the registry host (used when the environment does not name one). */
100
+ matches(registry: string): boolean;
101
+ /** Authenticate docker against `registry` (host part of the destination). */
102
+ login(registry: string): Promise<void>;
103
+ }
104
+
105
+ export class RegistryBackendError extends Error {
106
+ constructor(message: string) {
107
+ super(message);
108
+ this.name = "RegistryBackendError";
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Pick the login backend for one publish. Order: the step's `backend`, then the
114
+ * environment's `ctx.vars.publish.backend`, then the single backend whose
115
+ * `matches` claims the registry host, then the only backend there is.
116
+ */
117
+ export function selectRegistryBackend(
118
+ backends: readonly RegistryLoginBackend[],
119
+ registry: string,
120
+ named?: string,
121
+ ): RegistryLoginBackend {
122
+ const names = backends.map((b) => b.name).sort().join(", ") || "none";
123
+ if (named) {
124
+ const found = backends.find((b) => b.name === named);
125
+ if (!found) {
126
+ throw new RegistryBackendError(
127
+ `publish-image: no registry backend "${named}" is registered (available: ${names}); declare the lexicon that contributes it in chant.config.ts`,
128
+ );
129
+ }
130
+ return found;
131
+ }
132
+ const matched = backends.filter((b) => b.matches(registry));
133
+ if (matched.length === 1) return matched[0]!;
134
+ if (matched.length > 1) {
135
+ throw new RegistryBackendError(
136
+ `publish-image: registry "${registry}" matches several backends (${matched.map((b) => b.name).join(", ")}); set $env.publish.backend`,
137
+ );
138
+ }
139
+ if (backends.length === 1) return backends[0]!;
140
+ throw new RegistryBackendError(
141
+ `publish-image: cannot tell which registry backend serves "${registry}" (available: ${names}); set $env.publish.backend`,
142
+ );
143
+ }
144
+
145
+ /** Implemented by a capability that wants the plugins' registry backends injected when a registry is built. */
146
+ export interface RegistryBackendConsumer {
147
+ withRegistryBackends(backends: readonly RegistryLoginBackend[]): Capability<never, unknown>;
148
+ }
149
+
150
+ export function isRegistryBackendConsumer(value: unknown): value is RegistryBackendConsumer {
151
+ return typeof value === "object" && value !== null && typeof (value as RegistryBackendConsumer).withRegistryBackends === "function";
152
+ }
153
+
154
+ // ── publish-image ────────────────────────────────────────────────────────────
155
+
156
+ /**
157
+ * Attempt to `oras attach` one referrer document (SBOM or component BOM) to
158
+ * `imageRef`'s digest. Writes the document's bytes to a scratch file (`oras
159
+ * attach` reads from a file path, not stdin) and shells out through
160
+ * `runner`. Never throws — a failure here is reported back to the caller as
161
+ * `{ ok: false, reason }` so a missing/erroring `oras` never fails the
162
+ * publish itself (see `attachReferrers`'s doc comment for why).
163
+ */
164
+ async function attachOneReferrer(
165
+ runner: ProcessRunner,
166
+ imageRef: string,
167
+ doc: { bytes: string; mediaType: string },
168
+ label: string,
169
+ ): Promise<{ ok: boolean; reason?: string }> {
170
+ try {
171
+ const scratchFile = `/tmp/chant-referrer-attach/${label}-${Date.now()}.json`;
172
+ await runner.run(`mkdir -p ${q("/tmp/chant-referrer-attach")}`);
173
+ await runner.run(`printf '%s' ${q(doc.bytes)} > ${q(scratchFile)}`);
174
+ await runner.run(`oras attach --artifact-type ${q(doc.mediaType)} ${q(imageRef)} ${q(scratchFile)}:${q(doc.mediaType)}`);
175
+ return { ok: true };
176
+ } catch (err) {
177
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Best-effort OCI-referrer attach for a just-pushed image (#610): attaches
183
+ * `input.sbom` and/or `input.componentBom`, when supplied, to `imageRef` via
184
+ * `oras attach`. Guarded on `oras` availability — a missing binary is
185
+ * reported back as `{ attached: false, reason }` rather than thrown, since
186
+ * the image itself is already successfully promoted by the time this step
187
+ * runs (attach is additive registry-side convenience on top of the
188
+ * archive-carried copy, per this module's doc comment, never a condition the
189
+ * core promote-by-digest job depends on). Returns `undefined` when neither
190
+ * `input.sbom` nor `input.componentBom` was supplied — nothing to attach,
191
+ * skipped entirely, no `oras` call at all.
192
+ */
193
+ async function attachReferrers(
194
+ runner: ProcessRunner,
195
+ imageRef: string,
196
+ input: Pick<PublishImageInput, "sbom" | "componentBom">,
197
+ ): Promise<ReferrerAttachResult | undefined> {
198
+ if (!input.sbom && !input.componentBom) return undefined;
199
+
200
+ if (!(await runner.available("oras"))) {
201
+ return { attached: false, reason: `"oras" is not installed or not on PATH — required to attach OCI referrers` };
202
+ }
203
+
204
+ const results: Array<{ ok: boolean; reason?: string }> = [];
205
+ if (input.sbom) results.push(await attachOneReferrer(runner, imageRef, input.sbom, "sbom"));
206
+ if (input.componentBom) results.push(await attachOneReferrer(runner, imageRef, input.componentBom, "component-bom"));
207
+
208
+ const failed = results.find((r) => !r.ok);
209
+ return failed ? { attached: false, reason: failed.reason } : { attached: true };
210
+ }
211
+
212
+ export interface PublishImageOptions {
213
+ docker: DockerClient;
214
+ backends?: readonly RegistryLoginBackend[];
215
+ processRunner?: ProcessRunner;
216
+ }
217
+
218
+ export type PublishImageCapability = PublishImageBackend & RegistryBackendConsumer;
219
+
220
+ /**
221
+ * Build the `publish-image` verb over a docker client and the registry login
222
+ * backends available. Load, tag, log in via the selected backend, push, and
223
+ * return the pushed digest.
224
+ */
225
+ export function createPublishImageCapability(options: PublishImageOptions): PublishImageCapability {
226
+ const { docker } = options;
227
+ const backends = options.backends ?? [];
228
+ const processRunner = options.processRunner ?? defaultProcessRunner();
229
+ return {
230
+ kind: "publish-image",
231
+ withRegistryBackends(next) {
232
+ return createPublishImageCapability({ docker, processRunner, backends: next });
233
+ },
234
+ async run(ctx, input) {
235
+ if (!input.to) throw new Error(`publish-image "${input.from}": "to" (destination registry) is required`);
236
+ const repo = input.to.replace(/\/+$/, "");
237
+ const registry = repo.split("/")[0]!;
238
+ const envBackend = (ctx.vars?.publish as { backend?: unknown } | undefined)?.backend;
239
+ const backend = selectRegistryBackend(backends, registry, input.backend ?? (typeof envBackend === "string" ? envBackend : undefined));
240
+ // `docker load` reports the archived image's local reference: a name:tag
241
+ // for a tagged build, or an image id. It is a valid `docker tag` source
242
+ // but never a registry digest, so the promoted digest is read back from
243
+ // the push below.
244
+ const { digest: localRef } = await docker.load({ inFile: archiveRelativePath(input.from) });
245
+ // Publish under a tag: docker can neither tag nor push a digest-qualified
246
+ // reference, so the transport is a tag and the stable identifier is the
247
+ // digest the push returns.
248
+ const pushRef = `${repo}:latest`;
249
+ await docker.tag({ source: localRef, target: pushRef });
250
+ await backend.login(registry);
251
+ const { digest: pushed } = await docker.push({ image: pushRef });
252
+ const digest = pushed.includes("@") ? pushed.slice(pushed.indexOf("@") + 1) : pushed;
253
+ for (const tag of input.tags ?? []) {
254
+ const tagged = `${repo}:${tag}`;
255
+ await docker.tag({ source: localRef, target: tagged });
256
+ await docker.push({ image: tagged });
257
+ }
258
+ const referrerAttach = await attachReferrers(processRunner, `${repo}@${digest}`, input);
259
+ return { digest, uri: `${repo}@${digest}`, ...(referrerAttach ? { referrerAttach } : {}) };
260
+ },
261
+ };
262
+ }
263
+
264
+ /** The starter plugin's `publish-image`: real docker, backends injected from the loaded plugins. */
265
+ export const publishImageCapability: PublishImageCapability = createPublishImageCapability({ docker: realDocker });
@@ -21,8 +21,23 @@ import type { ChantConfig } from "./config";
21
21
  * `./workspace/promotion-order.ts`), and `canary: true` marks a step as the
22
22
  * canary step of that order. The canary is declared and surfaced; chant does
23
23
  * not yet act on it.
24
+ *
25
+ * `executor` names the forge and the committed workflow that deploys the
26
+ * environment (#4402), so a reader such as behold knows a release there runs
27
+ * through CI rather than a local `chant run`. chant declares and prints it
28
+ * (`chant workspace graph --composites`); it never dispatches it.
24
29
  */
25
- export type EnvironmentDeclaration = string | { name: string; endpoint?: string; canary?: boolean };
30
+ export type EnvironmentDeclaration = string | { name: string; endpoint?: string; canary?: boolean; executor?: EnvironmentExecutor };
31
+
32
+ /** The forges an environment's executor can name (#4402). */
33
+ export const EXECUTOR_FORGES = ["github", "gitlab", "forgejo"] as const;
34
+
35
+ /** The forge workflow that deploys one environment (#4402). */
36
+ export interface EnvironmentExecutor {
37
+ forge: (typeof EXECUTOR_FORGES)[number];
38
+ /** The committed workflow file the forge runs, such as `deploy-prod.yml` under `.github/workflows/`, or `.gitlab-ci.yml`. */
39
+ workflow: string;
40
+ }
26
41
 
27
42
  /**
28
43
  * `ownership.env`: a literal environment identity, or a reference to a
@@ -115,6 +130,18 @@ export function environmentEndpoint(environments: EnvironmentDeclaration[] | und
115
130
  return found && typeof found !== "string" ? found.endpoint : undefined;
116
131
  }
117
132
 
133
+ /**
134
+ * The executor `name` declares (#4402), resolved like {@link
135
+ * environmentEndpoint}: a literal entry wins over a pattern, and a literal
136
+ * entry with no executor does not fall through to a pattern's.
137
+ */
138
+ export function environmentExecutor(environments: EnvironmentDeclaration[] | undefined, name: string): EnvironmentExecutor | undefined {
139
+ const found =
140
+ environments?.find((e) => environmentName(e) === name) ??
141
+ environments?.find((e) => isEnvironmentPattern(environmentName(e)) && matchesEnvironmentPattern(environmentName(e), name));
142
+ return found && typeof found !== "string" ? found.executor : undefined;
143
+ }
144
+
118
145
  /** Whether an `ownership.env` entry is a build-parameter reference rather than a literal. */
119
146
  export function isOwnershipParamRef(env: OwnershipEnv | undefined): env is { param: string } {
120
147
  return typeof env === "object" && env !== null && typeof env.param === "string";
package/src/config.ts CHANGED
@@ -19,7 +19,9 @@ import { PublishDeclarationSchema, type PublishDeclaration } from "./lifecycle/p
19
19
  // needs only them stays clear of the loader below (#4356 follow-up).
20
20
  export {
21
21
  environmentEndpoint,
22
+ environmentExecutor,
22
23
  environmentName,
24
+ EXECUTOR_FORGES,
23
25
  environmentNames,
24
26
  isEnvironmentPattern,
25
27
  isOwnershipParamRef,
@@ -27,9 +29,10 @@ export {
27
29
  matchesEnvironmentPattern,
28
30
  resolveOwnershipStack,
29
31
  type EnvironmentDeclaration,
32
+ type EnvironmentExecutor,
30
33
  type OwnershipEnv,
31
34
  } from "./config-environments";
32
- import { isOwnershipParamRef, resolveOwnershipStack, type EnvironmentDeclaration, type OwnershipEnv } from "./config-environments";
35
+ import { EXECUTOR_FORGES, isOwnershipParamRef, resolveOwnershipStack, type EnvironmentDeclaration, type OwnershipEnv } from "./config-environments";
33
36
 
34
37
  /**
35
38
  * Zod schema for ChantConfig validation.
@@ -40,6 +43,10 @@ const EnvironmentEntrySchema = z.union([
40
43
  name: z.string().min(1),
41
44
  endpoint: z.string().min(1).optional(),
42
45
  canary: z.boolean().optional(),
46
+ executor: z.object({
47
+ forge: z.enum(EXECUTOR_FORGES),
48
+ workflow: z.string().min(1),
49
+ }).strict().optional(),
43
50
  }),
44
51
  ]);
45
52
 
@@ -69,6 +76,10 @@ export const ChantConfigSchema = z.object({
69
76
  // Strict like `run`: a misspelt `lint` would leave the gate on with nothing said.
70
77
  graph: z.object({
71
78
  lint: z.enum(["gate", "report"]).optional(),
79
+ reader: z.object({
80
+ module: z.string().min(1),
81
+ config: z.record(z.string(), z.unknown()).optional(),
82
+ }).strict().optional(),
72
83
  }).strict().optional(),
73
84
  buildParams: z.record(
74
85
  z.string(),
@@ -227,6 +238,18 @@ export interface ChantConfig {
227
238
  */
228
239
  graph?: {
229
240
  lint?: "gate" | "report";
241
+ /**
242
+ * A graph reader module (#4413): when set, `chant graph --format ir` loads
243
+ * `module` (an absolute path to an ES module whose default export is
244
+ * `(input: { dir, config }) => GraphIR`) and prints what it returns,
245
+ * instead of discovering lexicon entities. The reader project
246
+ * `chant workspace graph` writes for a kind whose `graph.reader` names a
247
+ * module sets it. A chant older than the key ignores it.
248
+ */
249
+ reader?: {
250
+ module: string;
251
+ config?: Record<string, unknown>;
252
+ };
230
253
  };
231
254
 
232
255
  /**