@intentius/chant 0.124.52 → 0.124.54

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 (584) hide show
  1. package/dist/audit/discover.d.ts.map +1 -1
  2. package/dist/audit/discover.js +3 -1
  3. package/dist/behaviour.d.ts +1 -1
  4. package/dist/behaviour.d.ts.map +1 -1
  5. package/dist/capabilities/templates.d.ts +65 -0
  6. package/dist/capabilities/templates.d.ts.map +1 -0
  7. package/dist/capabilities/templates.js +193 -0
  8. package/dist/capabilities/templates.json +145 -0
  9. package/dist/change-set-graph.d.ts +35 -3
  10. package/dist/change-set-graph.d.ts.map +1 -1
  11. package/dist/change-set-graph.js +60 -9
  12. package/dist/change-set.d.ts +65 -7
  13. package/dist/change-set.d.ts.map +1 -1
  14. package/dist/change-set.js +23 -12
  15. package/dist/ci-identity.d.ts +7 -2
  16. package/dist/ci-identity.d.ts.map +1 -1
  17. package/dist/ci-identity.js +4 -1
  18. package/dist/cli/commands/import-index.d.ts +52 -0
  19. package/dist/cli/commands/import-index.d.ts.map +1 -0
  20. package/dist/cli/commands/import-index.js +226 -0
  21. package/dist/cli/commands/lint.d.ts.map +1 -1
  22. package/dist/cli/commands/lint.js +4 -2
  23. package/dist/cli/handlers/components.d.ts.map +1 -1
  24. package/dist/cli/handlers/components.js +11 -2
  25. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  26. package/dist/cli/handlers/fan-out.js +16 -3
  27. package/dist/cli/handlers/graph.d.ts.map +1 -1
  28. package/dist/cli/handlers/graph.js +22 -4
  29. package/dist/cli/handlers/index-cloud.d.ts +14 -0
  30. package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
  31. package/dist/cli/handlers/index-cloud.js +22 -0
  32. package/dist/cli/handlers/index-diff.d.ts +18 -0
  33. package/dist/cli/handlers/index-diff.d.ts.map +1 -0
  34. package/dist/cli/handlers/index-diff.js +142 -0
  35. package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
  36. package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
  37. package/dist/cli/handlers/lifecycle-drift.js +64 -0
  38. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  39. package/dist/cli/handlers/lifecycle.js +188 -28
  40. package/dist/cli/handlers/misc.d.ts +9 -0
  41. package/dist/cli/handlers/misc.d.ts.map +1 -1
  42. package/dist/cli/handlers/misc.js +77 -0
  43. package/dist/cli/handlers/pr.d.ts.map +1 -1
  44. package/dist/cli/handlers/pr.js +15 -2
  45. package/dist/cli/handlers/promote.d.ts.map +1 -1
  46. package/dist/cli/handlers/promote.js +89 -15
  47. package/dist/cli/handlers/query.d.ts.map +1 -1
  48. package/dist/cli/handlers/query.js +28 -4
  49. package/dist/cli/handlers/scenario.d.ts.map +1 -1
  50. package/dist/cli/handlers/scenario.js +8 -7
  51. package/dist/cli/handlers/search.d.ts +34 -4
  52. package/dist/cli/handlers/search.d.ts.map +1 -1
  53. package/dist/cli/handlers/search.js +216 -36
  54. package/dist/cli/main.d.ts.map +1 -1
  55. package/dist/cli/main.js +55 -1
  56. package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
  57. package/dist/cli/mcp/lifecycle-tools.js +3 -2
  58. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  59. package/dist/cli/mcp/resource-handlers.js +4 -3
  60. package/dist/cli/mcp/workspace-tools.js +1 -1
  61. package/dist/cli/parse-args.d.ts.map +1 -1
  62. package/dist/cli/parse-args.js +55 -2
  63. package/dist/cli/registry.d.ts +19 -1
  64. package/dist/cli/registry.d.ts.map +1 -1
  65. package/dist/cloud-lister/aws.d.ts +35 -0
  66. package/dist/cloud-lister/aws.d.ts.map +1 -0
  67. package/dist/cloud-lister/aws.js +203 -0
  68. package/dist/cloud-lister/azure.d.ts +34 -0
  69. package/dist/cloud-lister/azure.d.ts.map +1 -0
  70. package/dist/cloud-lister/azure.js +129 -0
  71. package/dist/cloud-lister/common.d.ts +40 -0
  72. package/dist/cloud-lister/common.d.ts.map +1 -0
  73. package/dist/cloud-lister/common.js +83 -0
  74. package/dist/cloud-lister/credentials.d.ts +50 -0
  75. package/dist/cloud-lister/credentials.d.ts.map +1 -0
  76. package/dist/cloud-lister/credentials.js +86 -0
  77. package/dist/cloud-lister/fakes.d.ts +27 -0
  78. package/dist/cloud-lister/fakes.d.ts.map +1 -0
  79. package/dist/cloud-lister/fakes.js +50 -0
  80. package/dist/cloud-lister/gcp.d.ts +42 -0
  81. package/dist/cloud-lister/gcp.d.ts.map +1 -0
  82. package/dist/cloud-lister/gcp.js +143 -0
  83. package/dist/cloud-lister/guard.d.ts +49 -0
  84. package/dist/cloud-lister/guard.d.ts.map +1 -0
  85. package/dist/cloud-lister/guard.js +103 -0
  86. package/dist/cloud-lister/ids.d.ts +23 -0
  87. package/dist/cloud-lister/ids.d.ts.map +1 -0
  88. package/dist/cloud-lister/ids.js +42 -0
  89. package/dist/cloud-lister/index.d.ts +67 -0
  90. package/dist/cloud-lister/index.d.ts.map +1 -0
  91. package/dist/cloud-lister/index.js +102 -0
  92. package/dist/cloud-lister/kubernetes.d.ts +64 -0
  93. package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
  94. package/dist/cloud-lister/kubernetes.js +276 -0
  95. package/dist/cloud-lister/markers.d.ts +31 -0
  96. package/dist/cloud-lister/markers.d.ts.map +1 -0
  97. package/dist/cloud-lister/markers.js +73 -0
  98. package/dist/cloud-lister/mask.d.ts +31 -0
  99. package/dist/cloud-lister/mask.d.ts.map +1 -0
  100. package/dist/cloud-lister/mask.js +74 -0
  101. package/dist/cloud-lister/sigv4.d.ts +29 -0
  102. package/dist/cloud-lister/sigv4.d.ts.map +1 -0
  103. package/dist/cloud-lister/sigv4.js +54 -0
  104. package/dist/cloud-lister/transport.d.ts +55 -0
  105. package/dist/cloud-lister/transport.d.ts.map +1 -0
  106. package/dist/cloud-lister/transport.js +86 -0
  107. package/dist/cloud-lister/types.d.ts +121 -0
  108. package/dist/cloud-lister/types.d.ts.map +1 -0
  109. package/dist/cloud-lister/types.js +24 -0
  110. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  111. package/dist/components/capability-plugin-loader.js +6 -0
  112. package/dist/components/capability-plugin.d.ts +9 -0
  113. package/dist/components/capability-plugin.d.ts.map +1 -1
  114. package/dist/components/capability.d.ts +215 -0
  115. package/dist/components/capability.d.ts.map +1 -1
  116. package/dist/components/cli-support.d.ts +21 -0
  117. package/dist/components/cli-support.d.ts.map +1 -1
  118. package/dist/components/cli-support.js +58 -14
  119. package/dist/components/declared-reads.d.ts +38 -0
  120. package/dist/components/declared-reads.d.ts.map +1 -0
  121. package/dist/components/declared-reads.js +131 -0
  122. package/dist/components/deploy-units.d.ts.map +1 -1
  123. package/dist/components/deploy-units.js +4 -0
  124. package/dist/components/driver.d.ts +34 -1
  125. package/dist/components/driver.d.ts.map +1 -1
  126. package/dist/components/driver.js +66 -3
  127. package/dist/components/fan-out-run.d.ts +23 -1
  128. package/dist/components/fan-out-run.d.ts.map +1 -1
  129. package/dist/components/fan-out-run.js +82 -14
  130. package/dist/components/fan-out-support.d.ts +6 -0
  131. package/dist/components/fan-out-support.d.ts.map +1 -1
  132. package/dist/components/fan-out-support.js +4 -1
  133. package/dist/components/fan-out.d.ts +28 -0
  134. package/dist/components/fan-out.d.ts.map +1 -1
  135. package/dist/components/groups.d.ts +229 -0
  136. package/dist/components/groups.d.ts.map +1 -0
  137. package/dist/components/groups.js +760 -0
  138. package/dist/components/index.d.ts +6 -2
  139. package/dist/components/index.d.ts.map +1 -1
  140. package/dist/components/index.js +4 -0
  141. package/dist/components/plan-shape-conformance.d.ts +48 -0
  142. package/dist/components/plan-shape-conformance.d.ts.map +1 -0
  143. package/dist/components/plan-shape-conformance.js +122 -0
  144. package/dist/components/pr-run.d.ts +41 -4
  145. package/dist/components/pr-run.d.ts.map +1 -1
  146. package/dist/components/pr-run.js +209 -39
  147. package/dist/components/promote-rounds.d.ts +9 -0
  148. package/dist/components/promote-rounds.d.ts.map +1 -1
  149. package/dist/components/promote-rounds.js +23 -2
  150. package/dist/components/promote.d.ts +17 -0
  151. package/dist/components/promote.d.ts.map +1 -1
  152. package/dist/components/promote.js +36 -6
  153. package/dist/components/release-gate.d.ts +23 -1
  154. package/dist/components/release-gate.d.ts.map +1 -1
  155. package/dist/components/release-gate.js +97 -33
  156. package/dist/components/run-groups.d.ts +58 -0
  157. package/dist/components/run-groups.d.ts.map +1 -0
  158. package/dist/components/run-groups.js +78 -0
  159. package/dist/components/starter-plugin.d.ts +1 -0
  160. package/dist/components/starter-plugin.d.ts.map +1 -1
  161. package/dist/components/starter-plugin.js +6 -1
  162. package/dist/components/verbs/index.d.ts +1 -0
  163. package/dist/components/verbs/index.d.ts.map +1 -1
  164. package/dist/components/verbs/index.js +1 -0
  165. package/dist/components/verbs/member-outputs.d.ts +187 -0
  166. package/dist/components/verbs/member-outputs.d.ts.map +1 -0
  167. package/dist/components/verbs/member-outputs.js +218 -0
  168. package/dist/config.d.ts +60 -0
  169. package/dist/config.d.ts.map +1 -1
  170. package/dist/config.js +27 -0
  171. package/dist/deep-observation.d.ts +1 -13
  172. package/dist/deep-observation.d.ts.map +1 -1
  173. package/dist/deep-observation.js +5 -22
  174. package/dist/drift-record.d.ts +542 -0
  175. package/dist/drift-record.d.ts.map +1 -0
  176. package/dist/drift-record.js +886 -0
  177. package/dist/engine-facts.d.ts +144 -0
  178. package/dist/engine-facts.d.ts.map +1 -0
  179. package/dist/engine-facts.js +14 -0
  180. package/dist/graph-ir.d.ts +153 -2
  181. package/dist/graph-ir.d.ts.map +1 -1
  182. package/dist/graph-ir.js +224 -6
  183. package/dist/graph-refs.d.ts +145 -0
  184. package/dist/graph-refs.d.ts.map +1 -1
  185. package/dist/graph-refs.js +305 -0
  186. package/dist/graph-schema/fixtures/adopt.json +15 -0
  187. package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  188. package/dist/graph-schema/fixtures/index.json +68 -0
  189. package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  190. package/dist/graph-schema/fixtures.d.ts +23 -0
  191. package/dist/graph-schema/fixtures.d.ts.map +1 -0
  192. package/dist/graph-schema/fixtures.js +246 -0
  193. package/dist/graph-schema/generate.d.ts +15 -0
  194. package/dist/graph-schema/generate.d.ts.map +1 -0
  195. package/dist/graph-schema/generate.js +267 -0
  196. package/dist/graph-schema/graph.schema.json +1387 -0
  197. package/dist/health.schema.json +2 -1
  198. package/dist/identity.d.ts.map +1 -1
  199. package/dist/identity.js +2 -0
  200. package/dist/index.d.ts +1 -0
  201. package/dist/index.d.ts.map +1 -1
  202. package/dist/index.js +2 -0
  203. package/dist/instance-nodes.d.ts +159 -0
  204. package/dist/instance-nodes.d.ts.map +1 -0
  205. package/dist/instance-nodes.js +231 -0
  206. package/dist/lexicon.d.ts +72 -0
  207. package/dist/lexicon.d.ts.map +1 -1
  208. package/dist/lifecycle/adoption-file.d.ts +56 -0
  209. package/dist/lifecycle/adoption-file.d.ts.map +1 -0
  210. package/dist/lifecycle/adoption-file.js +123 -0
  211. package/dist/lifecycle/cloud-index.d.ts +186 -0
  212. package/dist/lifecycle/cloud-index.d.ts.map +1 -0
  213. package/dist/lifecycle/cloud-index.js +618 -0
  214. package/dist/lifecycle/derive-edges.d.ts +59 -0
  215. package/dist/lifecycle/derive-edges.d.ts.map +1 -0
  216. package/dist/lifecycle/derive-edges.js +126 -0
  217. package/dist/lifecycle/git.d.ts +10 -9
  218. package/dist/lifecycle/git.d.ts.map +1 -1
  219. package/dist/lifecycle/git.js +7 -1
  220. package/dist/lifecycle/identity-match.d.ts +133 -0
  221. package/dist/lifecycle/identity-match.d.ts.map +1 -0
  222. package/dist/lifecycle/identity-match.js +147 -0
  223. package/dist/lifecycle/index-diff.d.ts +224 -0
  224. package/dist/lifecycle/index-diff.d.ts.map +1 -0
  225. package/dist/lifecycle/index-diff.js +327 -0
  226. package/dist/lifecycle/index-document.d.ts +130 -0
  227. package/dist/lifecycle/index-document.d.ts.map +1 -0
  228. package/dist/lifecycle/index-document.js +189 -0
  229. package/dist/lifecycle/index-drift.d.ts +121 -0
  230. package/dist/lifecycle/index-drift.d.ts.map +1 -0
  231. package/dist/lifecycle/index-drift.js +178 -0
  232. package/dist/lifecycle/index-import.d.ts +168 -0
  233. package/dist/lifecycle/index-import.d.ts.map +1 -0
  234. package/dist/lifecycle/index-import.js +515 -0
  235. package/dist/lifecycle/index-join.d.ts +129 -0
  236. package/dist/lifecycle/index-join.d.ts.map +1 -0
  237. package/dist/lifecycle/index-join.js +248 -0
  238. package/dist/lifecycle/index.d.ts +4 -0
  239. package/dist/lifecycle/index.d.ts.map +1 -1
  240. package/dist/lifecycle/index.js +4 -0
  241. package/dist/lifecycle/observation-baseline.d.ts +61 -21
  242. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  243. package/dist/lifecycle/observation-baseline.js +144 -19
  244. package/dist/lifecycle/observe.d.ts.map +1 -1
  245. package/dist/lifecycle/observe.js +6 -1
  246. package/dist/lifecycle/query.d.ts +45 -4
  247. package/dist/lifecycle/query.d.ts.map +1 -1
  248. package/dist/lifecycle/query.js +89 -7
  249. package/dist/lifecycle/replay.d.ts +10 -3
  250. package/dist/lifecycle/replay.d.ts.map +1 -1
  251. package/dist/lifecycle/replay.js +10 -6
  252. package/dist/lifecycle/scenario.d.ts +4 -3
  253. package/dist/lifecycle/scenario.d.ts.map +1 -1
  254. package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
  255. package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
  256. package/dist/lifecycle/snapshot-store-fake.js +21 -0
  257. package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
  258. package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
  259. package/dist/lifecycle/snapshot-store-s3.js +254 -0
  260. package/dist/lifecycle/snapshot-store.d.ts +170 -0
  261. package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
  262. package/dist/lifecycle/snapshot-store.js +832 -0
  263. package/dist/lifecycle/snapshot.d.ts +11 -0
  264. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  265. package/dist/lifecycle/snapshot.js +18 -5
  266. package/dist/lifecycle/type-listing.d.ts +148 -0
  267. package/dist/lifecycle/type-listing.d.ts.map +1 -0
  268. package/dist/lifecycle/type-listing.js +166 -0
  269. package/dist/lint/component-checks.d.ts +15 -1
  270. package/dist/lint/component-checks.d.ts.map +1 -1
  271. package/dist/lint/component-checks.js +1 -0
  272. package/dist/lint/presets/relaxed.json +2 -1
  273. package/dist/lint/presets/strict.json +2 -1
  274. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
  275. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
  276. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
  277. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
  278. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
  279. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
  280. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
  281. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
  282. package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
  283. package/dist/lint/rules/comp/index.d.ts +2 -1
  284. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  285. package/dist/lint/rules/comp/index.js +4 -1
  286. package/dist/observation.d.ts +19 -2
  287. package/dist/observation.d.ts.map +1 -1
  288. package/dist/observation.js +18 -1
  289. package/dist/op/activities/activity-contracts.d.ts +1 -1
  290. package/dist/op/activities/activity-contracts.js +1 -1
  291. package/dist/op/activities/change-set.d.ts +6 -4
  292. package/dist/op/activities/change-set.d.ts.map +1 -1
  293. package/dist/op/activities/change-set.js +4 -2
  294. package/dist/op/activity-failure.d.ts +27 -0
  295. package/dist/op/activity-failure.d.ts.map +1 -1
  296. package/dist/op/activity-failure.js +31 -0
  297. package/dist/op/index.d.ts +2 -1
  298. package/dist/op/index.d.ts.map +1 -1
  299. package/dist/op/index.js +1 -1
  300. package/dist/op/local-executor.d.ts +5 -1
  301. package/dist/op/local-executor.d.ts.map +1 -1
  302. package/dist/op/local-executor.js +22 -8
  303. package/dist/pr-loop.d.ts +56 -1
  304. package/dist/pr-loop.d.ts.map +1 -1
  305. package/dist/pr-loop.js +51 -1
  306. package/dist/pulumi-urn.d.ts +101 -0
  307. package/dist/pulumi-urn.d.ts.map +1 -0
  308. package/dist/pulumi-urn.js +131 -0
  309. package/dist/release-floors.d.ts +23 -0
  310. package/dist/release-floors.d.ts.map +1 -0
  311. package/dist/release-floors.js +22 -0
  312. package/dist/sensitive-key.d.ts +23 -0
  313. package/dist/sensitive-key.d.ts.map +1 -0
  314. package/dist/sensitive-key.js +31 -0
  315. package/dist/wave-runtime.mjs +234 -234
  316. package/dist/workspace/adoption-report.d.ts +53 -0
  317. package/dist/workspace/adoption-report.d.ts.map +1 -0
  318. package/dist/workspace/adoption-report.js +76 -0
  319. package/dist/workspace/capabilities-contract.d.ts +7 -0
  320. package/dist/workspace/capabilities-contract.d.ts.map +1 -1
  321. package/dist/workspace/capabilities-contract.js +2 -1
  322. package/dist/workspace/capabilities.schema.json +98 -0
  323. package/dist/workspace/change-set.schema.json +42 -3
  324. package/dist/workspace/ci-cloud-identity.d.ts +13 -1
  325. package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
  326. package/dist/workspace/ci-cloud-identity.js +30 -1
  327. package/dist/workspace/compose-graph.d.ts +10 -1
  328. package/dist/workspace/compose-graph.d.ts.map +1 -1
  329. package/dist/workspace/compose-graph.js +6 -1
  330. package/dist/workspace/conformance/index.d.ts +1 -1
  331. package/dist/workspace/conformance/index.d.ts.map +1 -1
  332. package/dist/workspace/conformance/index.js +6 -1
  333. package/dist/workspace/drift-cli.d.ts +81 -0
  334. package/dist/workspace/drift-cli.d.ts.map +1 -0
  335. package/dist/workspace/drift-cli.js +317 -0
  336. package/dist/workspace/drift.schema.json +672 -0
  337. package/dist/workspace/graph-cli.d.ts +2 -0
  338. package/dist/workspace/graph-cli.d.ts.map +1 -1
  339. package/dist/workspace/graph-cli.js +3 -0
  340. package/dist/workspace/graph.schema.json +72 -0
  341. package/dist/workspace/init.d.ts +5 -1
  342. package/dist/workspace/init.d.ts.map +1 -1
  343. package/dist/workspace/init.js +55 -9
  344. package/dist/workspace/ledger.schema.json +1 -1
  345. package/dist/workspace/nested-graph.d.ts.map +1 -1
  346. package/dist/workspace/nested-graph.js +2 -0
  347. package/dist/workspace/pr-report.schema.json +51 -2
  348. package/dist/workspace/promote-waves.d.ts +112 -0
  349. package/dist/workspace/promote-waves.d.ts.map +1 -0
  350. package/dist/workspace/promote-waves.js +290 -0
  351. package/dist/workspace/promote.d.ts +84 -2
  352. package/dist/workspace/promote.d.ts.map +1 -1
  353. package/dist/workspace/promote.js +347 -61
  354. package/dist/workspace/reason-codes.d.ts +12 -0
  355. package/dist/workspace/reason-codes.d.ts.map +1 -1
  356. package/dist/workspace/reason-codes.js +14 -0
  357. package/dist/workspace/search-contract.d.ts +3 -3
  358. package/dist/workspace/search-contract.d.ts.map +1 -1
  359. package/dist/workspace/search-contract.js +2 -0
  360. package/dist/workspace/search.schema.json +103 -5
  361. package/package.json +14 -1
  362. package/src/__fixtures__/drift-record/README.md +58 -0
  363. package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
  364. package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
  365. package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
  366. package/src/__fixtures__/drift-record/component-status.json +91 -0
  367. package/src/__fixtures__/drift-record/deep-diff.json +259 -0
  368. package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
  369. package/src/__fixtures__/drift-record/live-diff.json +125 -0
  370. package/src/audit/discover.e2e.test.ts +14 -0
  371. package/src/audit/discover.ts +3 -1
  372. package/src/behaviour.ts +1 -1
  373. package/src/capabilities/templates.json +145 -0
  374. package/src/capabilities/templates.test.ts +212 -0
  375. package/src/capabilities/templates.ts +204 -0
  376. package/src/change-set-bundle.test.ts +83 -0
  377. package/src/change-set-graph.ts +80 -9
  378. package/src/change-set.test.ts +24 -0
  379. package/src/change-set.ts +77 -14
  380. package/src/ci-identity-targets.test.ts +23 -0
  381. package/src/ci-identity.ts +10 -4
  382. package/src/cli/commands/import-index.test.ts +117 -0
  383. package/src/cli/commands/import-index.ts +265 -0
  384. package/src/cli/commands/lint.ts +4 -2
  385. package/src/cli/handlers/components.test.ts +9 -0
  386. package/src/cli/handlers/components.ts +12 -2
  387. package/src/cli/handlers/fan-out.ts +17 -3
  388. package/src/cli/handlers/graph.test.ts +42 -0
  389. package/src/cli/handlers/graph.ts +26 -4
  390. package/src/cli/handlers/index-cloud.ts +32 -0
  391. package/src/cli/handlers/index-diff.test.ts +68 -0
  392. package/src/cli/handlers/index-diff.ts +146 -0
  393. package/src/cli/handlers/ledger-root-member.test.ts +161 -0
  394. package/src/cli/handlers/lifecycle-drift.ts +68 -0
  395. package/src/cli/handlers/lifecycle.test.ts +33 -11
  396. package/src/cli/handlers/lifecycle.ts +205 -30
  397. package/src/cli/handlers/misc.ts +73 -0
  398. package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
  399. package/src/cli/handlers/pr.ts +14 -2
  400. package/src/cli/handlers/promote.test.ts +64 -0
  401. package/src/cli/handlers/promote.ts +81 -6
  402. package/src/cli/handlers/query.ts +27 -4
  403. package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
  404. package/src/cli/handlers/run-groups-cli.test.ts +229 -0
  405. package/src/cli/handlers/scenario.test.ts +13 -0
  406. package/src/cli/handlers/scenario.ts +8 -7
  407. package/src/cli/handlers/search-index.test.ts +255 -0
  408. package/src/cli/handlers/search-json.test.ts +1 -1
  409. package/src/cli/handlers/search-ownership.test.ts +194 -0
  410. package/src/cli/handlers/search.ts +271 -38
  411. package/src/cli/main.test.ts +7 -0
  412. package/src/cli/main.ts +56 -1
  413. package/src/cli/mcp/lifecycle-tools.ts +3 -2
  414. package/src/cli/mcp/resource-handlers.ts +4 -3
  415. package/src/cli/mcp/workspace-tools.ts +1 -1
  416. package/src/cli/parse-args.ts +40 -2
  417. package/src/cli/registry.ts +19 -1
  418. package/src/cloud-lister/aws.test.ts +202 -0
  419. package/src/cloud-lister/aws.ts +226 -0
  420. package/src/cloud-lister/azure.test.ts +132 -0
  421. package/src/cloud-lister/azure.ts +142 -0
  422. package/src/cloud-lister/common.ts +115 -0
  423. package/src/cloud-lister/credentials.ts +110 -0
  424. package/src/cloud-lister/fakes.ts +66 -0
  425. package/src/cloud-lister/gcp.test.ts +121 -0
  426. package/src/cloud-lister/gcp.ts +159 -0
  427. package/src/cloud-lister/guard.test.ts +198 -0
  428. package/src/cloud-lister/guard.ts +111 -0
  429. package/src/cloud-lister/ids.ts +54 -0
  430. package/src/cloud-lister/index.ts +134 -0
  431. package/src/cloud-lister/kubernetes.test.ts +160 -0
  432. package/src/cloud-lister/kubernetes.ts +284 -0
  433. package/src/cloud-lister/markers.ts +74 -0
  434. package/src/cloud-lister/mask.ts +77 -0
  435. package/src/cloud-lister/shape.test.ts +81 -0
  436. package/src/cloud-lister/sigv4.ts +70 -0
  437. package/src/cloud-lister/transport.ts +118 -0
  438. package/src/cloud-lister/types.ts +135 -0
  439. package/src/components/capability-plugin-loader.ts +4 -0
  440. package/src/components/capability-plugin.ts +10 -0
  441. package/src/components/capability.ts +229 -0
  442. package/src/components/cli-support.ts +70 -14
  443. package/src/components/declared-reads.test.ts +103 -0
  444. package/src/components/declared-reads.ts +134 -0
  445. package/src/components/deploy-units.ts +4 -0
  446. package/src/components/driver.ts +89 -4
  447. package/src/components/fan-out-run.ts +95 -15
  448. package/src/components/fan-out-support.ts +10 -1
  449. package/src/components/fan-out.ts +30 -0
  450. package/src/components/groups.test.ts +320 -0
  451. package/src/components/groups.ts +880 -0
  452. package/src/components/index.ts +42 -0
  453. package/src/components/plan-shape-conformance.ts +130 -0
  454. package/src/components/pr-env-run-bundle.test.ts +2 -0
  455. package/src/components/pr-run.test.ts +516 -3
  456. package/src/components/pr-run.ts +231 -41
  457. package/src/components/promote-rounds.ts +25 -2
  458. package/src/components/promote.ts +55 -5
  459. package/src/components/registry.test.ts +1 -1
  460. package/src/components/release-gate.ts +115 -30
  461. package/src/components/release-groups.test.ts +362 -0
  462. package/src/components/run-groups.ts +113 -0
  463. package/src/components/starter-plugin.ts +6 -0
  464. package/src/components/verbs/index.ts +1 -0
  465. package/src/components/verbs/member-outputs.test.ts +205 -0
  466. package/src/components/verbs/member-outputs.ts +308 -0
  467. package/src/config.ts +44 -0
  468. package/src/deep-observation.ts +5 -23
  469. package/src/drift-record.test.ts +596 -0
  470. package/src/drift-record.ts +1206 -0
  471. package/src/engine-facts.guard.test.ts +111 -0
  472. package/src/engine-facts.test.ts +178 -0
  473. package/src/engine-facts.ts +154 -0
  474. package/src/gated-waves-bundle.test.ts +2 -0
  475. package/src/graph-ir-live.test.ts +92 -0
  476. package/src/graph-ir.ts +317 -7
  477. package/src/graph-refs.ts +416 -0
  478. package/src/graph-schema/fixtures/adopt.json +15 -0
  479. package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  480. package/src/graph-schema/fixtures/index.json +68 -0
  481. package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  482. package/src/graph-schema/fixtures.ts +267 -0
  483. package/src/graph-schema/generate.ts +263 -0
  484. package/src/graph-schema/graph-schema.test.ts +153 -0
  485. package/src/graph-schema/graph.schema.json +1387 -0
  486. package/src/health.schema.json +2 -1
  487. package/src/identity.ts +2 -0
  488. package/src/index.ts +2 -0
  489. package/src/instance-nodes.ts +288 -0
  490. package/src/lexicon.ts +76 -0
  491. package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
  492. package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
  493. package/src/lifecycle/adoption-file.test.ts +100 -0
  494. package/src/lifecycle/adoption-file.ts +154 -0
  495. package/src/lifecycle/cloud-index.test.ts +517 -0
  496. package/src/lifecycle/cloud-index.ts +736 -0
  497. package/src/lifecycle/derive-edges.test.ts +193 -0
  498. package/src/lifecycle/derive-edges.ts +182 -0
  499. package/src/lifecycle/git.ts +10 -4
  500. package/src/lifecycle/identity-match.test.ts +308 -0
  501. package/src/lifecycle/identity-match.ts +257 -0
  502. package/src/lifecycle/index-diff.test.ts +198 -0
  503. package/src/lifecycle/index-diff.ts +476 -0
  504. package/src/lifecycle/index-document.test.ts +64 -0
  505. package/src/lifecycle/index-document.ts +299 -0
  506. package/src/lifecycle/index-drift.test.ts +191 -0
  507. package/src/lifecycle/index-drift.ts +304 -0
  508. package/src/lifecycle/index-import.test.ts +255 -0
  509. package/src/lifecycle/index-import.ts +634 -0
  510. package/src/lifecycle/index-join.test.ts +250 -0
  511. package/src/lifecycle/index-join.ts +304 -0
  512. package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
  513. package/src/lifecycle/index.ts +4 -0
  514. package/src/lifecycle/member-ledger.test.ts +25 -7
  515. package/src/lifecycle/observation-baseline.test.ts +72 -1
  516. package/src/lifecycle/observation-baseline.ts +190 -27
  517. package/src/lifecycle/observe.ts +6 -1
  518. package/src/lifecycle/query.test.ts +69 -1
  519. package/src/lifecycle/query.ts +103 -10
  520. package/src/lifecycle/replay.test.ts +9 -5
  521. package/src/lifecycle/replay.ts +9 -5
  522. package/src/lifecycle/scenario.ts +4 -3
  523. package/src/lifecycle/snapshot-store-fake.ts +29 -0
  524. package/src/lifecycle/snapshot-store-s3.ts +316 -0
  525. package/src/lifecycle/snapshot-store.test.ts +385 -0
  526. package/src/lifecycle/snapshot-store.ts +981 -0
  527. package/src/lifecycle/snapshot.test.ts +13 -0
  528. package/src/lifecycle/snapshot.ts +23 -6
  529. package/src/lifecycle/type-listing.test.ts +166 -0
  530. package/src/lifecycle/type-listing.ts +309 -0
  531. package/src/lint/component-checks.ts +17 -1
  532. package/src/lint/config.test.ts +3 -0
  533. package/src/lint/presets/relaxed.json +2 -1
  534. package/src/lint/presets/strict.json +2 -1
  535. package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
  536. package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
  537. package/src/lint/rules/comp/comp.test.ts +2 -1
  538. package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
  539. package/src/lint/rules/comp/comp008.test.ts +77 -0
  540. package/src/lint/rules/comp/index.ts +4 -1
  541. package/src/observation-edges.test.ts +33 -0
  542. package/src/observation.ts +36 -1
  543. package/src/op/activities/activity-contracts.ts +1 -1
  544. package/src/op/activities/change-set.ts +8 -5
  545. package/src/op/activity-failure.ts +36 -0
  546. package/src/op/index.ts +2 -1
  547. package/src/op/local-executor.test.ts +80 -0
  548. package/src/op/local-executor.ts +27 -9
  549. package/src/pr-loop-bundle.test.ts +5 -2
  550. package/src/pr-loop.test.ts +43 -0
  551. package/src/pr-loop.ts +111 -2
  552. package/src/pulumi-urn.ts +151 -0
  553. package/src/release-floors.ts +22 -0
  554. package/src/sensitive-key.ts +34 -0
  555. package/src/workspace/adoption-report.test.ts +61 -0
  556. package/src/workspace/adoption-report.ts +103 -0
  557. package/src/workspace/capabilities-contract.test.ts +4 -0
  558. package/src/workspace/capabilities-contract.ts +9 -1
  559. package/src/workspace/capabilities.schema.json +98 -0
  560. package/src/workspace/change-set.schema.json +42 -3
  561. package/src/workspace/ci-cloud-identity.ts +31 -1
  562. package/src/workspace/compose-graph.test.ts +12 -0
  563. package/src/workspace/compose-graph.ts +12 -1
  564. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  565. package/src/workspace/conformance/index.ts +6 -1
  566. package/src/workspace/drift-cli.test.ts +65 -0
  567. package/src/workspace/drift-cli.ts +365 -0
  568. package/src/workspace/drift.schema.json +672 -0
  569. package/src/workspace/graph-cli.ts +5 -0
  570. package/src/workspace/graph.schema.json +72 -0
  571. package/src/workspace/init.test.ts +38 -1
  572. package/src/workspace/init.ts +50 -8
  573. package/src/workspace/intent-gaps.test.ts +18 -2
  574. package/src/workspace/ledger.schema.json +1 -1
  575. package/src/workspace/nested-graph.ts +1 -0
  576. package/src/workspace/pr-report.schema.json +51 -2
  577. package/src/workspace/promote-waves.test.ts +296 -0
  578. package/src/workspace/promote-waves.ts +340 -0
  579. package/src/workspace/promote.test.ts +158 -4
  580. package/src/workspace/promote.ts +398 -57
  581. package/src/workspace/reason-codes.test.ts +4 -1
  582. package/src/workspace/reason-codes.ts +14 -0
  583. package/src/workspace/search-contract.ts +2 -0
  584. package/src/workspace/search.schema.json +103 -5
@@ -0,0 +1,113 @@
1
+ /**
2
+ * What a promote under a release, `components fan-out` and `run --components`
3
+ * say about the groups they applied as one (ws-107, #4465), as `pr-report`
4
+ * says it for a pull request since #4446: each group with its members, the
5
+ * wave it ran in, its digest and the reads its apply computes, how each
6
+ * member ended, and why each component that could have grouped did not.
7
+ */
8
+
9
+ import type { ChangeSetDocument } from "../change-set";
10
+ import type { GroupMemberStatus } from "./capability";
11
+ import type { FanOutGroup, FanOutGroupFallback, FanOutPlan } from "./fan-out";
12
+
13
+ /** One group a run applied as one. */
14
+ export interface RunGroupReport {
15
+ /** The kind of the capability every member's one step names. */
16
+ capability: string;
17
+ /** The members, in apply order: upstreams first. */
18
+ components: string[];
19
+ /** The 1-based wave of the grouped fan-out it ran in. */
20
+ wave: number;
21
+ /** The group digest folded into each member's `planDigest`; `null` when it failed to plan. Absent when the run planned it in its wave. */
22
+ digest?: string | null;
23
+ /** Why the group failed to plan. */
24
+ error?: string;
25
+ /** Each read of another member its plan did not know, by change-set member, which the group's apply computes. Absent when none. */
26
+ reads?: Array<{ member: string; ref: string }>;
27
+ /** How each member's part ended, as the capability said. Absent when the group did not run. */
28
+ outcomes?: Record<string, GroupMemberStatus>;
29
+ }
30
+
31
+ export interface RunGroupsReport {
32
+ groups?: RunGroupReport[];
33
+ fallbacks?: FanOutGroupFallback[];
34
+ }
35
+
36
+ /**
37
+ * The report of `grouped`'s groups. `planned` are the groups a release gate's
38
+ * plan planned (`ReleasePlan.groups`), with their digests; `doc` is the
39
+ * change set they are in, which names each member's reads; `outcomes` is the
40
+ * run's `groupOutcomes`.
41
+ */
42
+ export function runGroupsReport(input: {
43
+ grouped: FanOutPlan | undefined;
44
+ planned?: ReadonlyArray<FanOutGroup & { digest: string | null; error?: string }>;
45
+ doc?: ChangeSetDocument;
46
+ outcomes?: Record<string, GroupMemberStatus>;
47
+ }): RunGroupsReport {
48
+ const waves = input.grouped?.waves ?? [];
49
+ const waveOf = new Map(waves.flatMap((w, i) => w.map((name) => [name, i + 1] as const)));
50
+ const groups: Array<FanOutGroup & { digest?: string | null; error?: string }> = input.planned?.length ? [...input.planned] : [...(input.grouped?.groups ?? [])];
51
+ const out: RunGroupReport[] = groups.map((g) => {
52
+ const reads = g.digest
53
+ ? (input.doc?.members ?? []).filter((m) => m.group?.digest === g.digest).flatMap((m) => m.group!.reads.map((ref) => ({ member: m.member, ref })))
54
+ : [];
55
+ const outcomes = Object.fromEntries(g.components.filter((c) => input.outcomes?.[c] !== undefined).map((c) => [c, input.outcomes![c]!]));
56
+ return {
57
+ capability: g.capability,
58
+ components: [...g.components],
59
+ wave: waveOf.get(g.components[0]!) ?? 0,
60
+ ...(g.digest !== undefined ? { digest: g.digest } : {}),
61
+ ...(g.error !== undefined ? { error: g.error } : {}),
62
+ ...(reads.length > 0 ? { reads } : {}),
63
+ ...(Object.keys(outcomes).length > 0 ? { outcomes } : {}),
64
+ };
65
+ });
66
+ const fallbacks = input.grouped?.fallbacks ?? [];
67
+ return { ...(out.length > 0 ? { groups: out } : {}), ...(fallbacks.length > 0 ? { fallbacks: [...fallbacks] } : {}) };
68
+ }
69
+
70
+ /** `a`, `b` and `c`. */
71
+ const listed = (items: string[]): string => (items.length > 1 ? `${items.slice(0, -1).join(", ")} and ${items.at(-1)}` : (items[0] ?? ""));
72
+
73
+ const OUTCOME_WORDS: Record<GroupMemberStatus, string> = {
74
+ applied: "applied",
75
+ "no-changes": "had no changes",
76
+ partial: "applied in part, and what applied stays",
77
+ failed: "failed",
78
+ "not-started": "did not start",
79
+ };
80
+
81
+ /**
82
+ * The report as lines for a person: one per group naming its members, wave,
83
+ * digest and the reads its apply computes, then how each member ended, and,
84
+ * when the apply stopped part way, what `again` (the command to run again)
85
+ * plans next. Then why the components that could have grouped did not; a
86
+ * fallback of kind `off` is not printed.
87
+ */
88
+ export function renderRunGroups(report: RunGroupsReport, again: string): string[] {
89
+ const out: string[] = [];
90
+ for (const g of report.groups ?? []) {
91
+ const reads = (g.reads ?? []).map((r) => `${r.member} reads ${r.ref}`);
92
+ out.push(
93
+ `${listed(g.components)} plan and apply as one (${g.capability}, wave ${g.wave}${g.digest ? `, group ${g.digest}` : ""})` +
94
+ (g.error ? `; the group failed to plan, so nothing of it applied: ${g.error}` : reads.length > 0 ? `; ${listed(reads)}, known when the group applies` : ""),
95
+ );
96
+ const outcomes = g.outcomes ?? {};
97
+ const ran = g.components.filter((c) => outcomes[c] !== undefined);
98
+ if (ran.length === 0) continue;
99
+ out.push(` ${ran.map((c) => `${c} ${OUTCOME_WORDS[outcomes[c]!]}`).join("; ")}`);
100
+ const left = ran.filter((c) => outcomes[c] !== "applied" && outcomes[c] !== "no-changes");
101
+ if (left.length > 0) {
102
+ out.push(` the group's apply stopped part way and nothing that applied is undone; ${again} plans ${listed(left)} again, as one group where they still join`);
103
+ }
104
+ }
105
+ const byWhy = new Map<string, string[]>();
106
+ for (const f of report.fallbacks ?? []) {
107
+ if (f.kind === "off") continue;
108
+ const why = `(${f.kind}): ${f.reason}`;
109
+ byWhy.set(why, [...(byWhy.get(why) ?? []), f.component]);
110
+ }
111
+ for (const [why, names] of byWhy) out.push(`planned one by one, not as a group: ${names.join(", ")} ${why}`);
112
+ return out;
113
+ }
@@ -47,6 +47,7 @@ import {
47
47
  wranglerDeployCapability,
48
48
  wranglerVersionsPromoteCapability,
49
49
  r2SyncCapability,
50
+ memberOutputsStarterCapability,
50
51
  } from "./verbs/index";
51
52
  import { ownPackageVersion, type CapabilityPlugin } from "./capability-plugin";
52
53
 
@@ -82,6 +83,10 @@ export const STARTER_VERB_FAMILIES = {
82
83
  // there is no cloudflare lexicon for these three verbs to live in. Revisit
83
84
  // if/when the zone-plane lexicon (#1294) lands.
84
85
  cloudflare: ["wrangler-deploy", "wrangler-versions-promote", "r2-sync"],
86
+ // Another workspace member's outputs, read through the engine it runs on
87
+ // (#4455). One agnostic verb; each engine's lexicon contributes a source
88
+ // (`CapabilityPlugin.engineOutputSources`).
89
+ engineOutputs: ["member-outputs"],
85
90
  } as const;
86
91
 
87
92
  /**
@@ -112,6 +117,7 @@ function starterCapabilities(): Array<Capability<never, unknown>> {
112
117
  wranglerDeployCapability,
113
118
  wranglerVersionsPromoteCapability,
114
119
  r2SyncCapability,
120
+ memberOutputsStarterCapability,
115
121
  ] as unknown as Array<Capability<never, unknown>>;
116
122
  }
117
123
 
@@ -53,6 +53,7 @@ export * from "./verify";
53
53
  export * from "./run-agent";
54
54
  export * from "./wrangler";
55
55
  export * from "./r2-sync";
56
+ export * from "./member-outputs";
56
57
  export * from "./vuln-scan";
57
58
  export * from "./vex";
58
59
  export * from "./license-policy";
@@ -0,0 +1,205 @@
1
+ /**
2
+ * `member-outputs` (#4455): a chant member's parameter planned against
3
+ * another workspace member's output, read through that member's engine.
4
+ *
5
+ * The estate is the three-cloud demo's: `dns` reads `azureEndpoint`, which
6
+ * the `azure` member publishes. `azure` is a choudoufu member, read through
7
+ * a fake engine source standing in for the terraform lexicon's
8
+ * `choudoufuOutputSource`; `records` is dns's deploy, a fake step that
9
+ * plans its parameters the way `cfn-deploy` does (a hole when an input is
10
+ * not known yet).
11
+ */
12
+
13
+ import { describe, expect, test } from "vitest";
14
+ import { CapabilityRegistry, type DeployContext } from "../capability";
15
+ import { registerCapabilityPlugins } from "../capability-plugin-loader";
16
+ import type { CapabilityPlugin } from "../capability-plugin";
17
+ import { starterCapabilityPlugin } from "../starter-plugin";
18
+ import { planFanOut } from "../fan-out";
19
+ import { applyPrSet, approvalOf, decidePrGate, planPrSet, readOnlyLedger, type PrPlanSet } from "../pr-run";
20
+ import { memoryGateLedgerPort } from "../../op/gate";
21
+ import { computePlanDigest } from "../../lifecycle/plan-digest";
22
+ import { deployUnits } from "../deploy-units";
23
+ import type { DriverComponent } from "../driver";
24
+ import {
25
+ EngineOutputSourceMissingError,
26
+ MEMBER_OUTPUTS_KIND,
27
+ memberOutputsCapability,
28
+ memberOutputsComponent,
29
+ memberOutputsMemberName,
30
+ MemberOutputsPendingError,
31
+ type EngineMemberRef,
32
+ type EngineOutputSource,
33
+ type EnginePlannedOutputs,
34
+ } from "./member-outputs";
35
+
36
+ interface RecordsInput {
37
+ inputs: Record<string, unknown>;
38
+ }
39
+
40
+ /** dns's two components: the azure member's outputs, and the records reading `azureEndpoint`. */
41
+ const ESTATE: DriverComponent[] = [
42
+ memberOutputsComponent("azure") as unknown as DriverComponent,
43
+ {
44
+ name: "records",
45
+ dependsOn: ["azure"],
46
+ deploy: [{ phase: "Apply", steps: [{ kind: "fake-records", inputs: { azureEndpoint: { stackOutput: { stack: "azure", name: "azureEndpoint" } } } }] }],
47
+ },
48
+ ];
49
+
50
+ const WORKSPACE = "/ws";
51
+ const lookup = async (_cwd: string, member: string): Promise<EngineMemberRef> => {
52
+ if (member !== "azure") throw new Error(`no member ${member}`);
53
+ return { member, kind: "choudoufu", dir: `${WORKSPACE}/azure`, workspaceRoot: WORKSPACE };
54
+ };
55
+
56
+ function world() {
57
+ const w = {
58
+ /** What the azure estate's outputs are now. */
59
+ applied: { azureEndpoint: "web.old.azurecontainerapps.io" } as Record<string, unknown>,
60
+ /** What azure's plan says. */
61
+ plan: { known: { azureEndpoint: "web.old.azurecontainerapps.io" }, unknown: [] as string[], planDigest: "sha256:azure-plan", changes: false } as EnginePlannedOutputs,
62
+ /** What each plan of records saw. */
63
+ recordsPlanned: [] as Array<{ inputs: Record<string, unknown>; unresolved?: string[] }>,
64
+ recordsRan: [] as Array<Record<string, unknown>>,
65
+ refs: [] as EngineMemberRef[],
66
+ };
67
+ const source: EngineOutputSource = {
68
+ kinds: ["choudoufu"],
69
+ async applied(_ctx, ref) {
70
+ w.refs.push(ref);
71
+ return { ...w.applied };
72
+ },
73
+ async planned(_ctx, ref) {
74
+ w.refs.push(ref);
75
+ return { ...w.plan, known: { ...w.plan.known }, unknown: [...w.plan.unknown] };
76
+ },
77
+ };
78
+ const plugin: CapabilityPlugin = {
79
+ name: "fake-dns",
80
+ version: "0.0.0",
81
+ capabilities: () =>
82
+ [
83
+ {
84
+ kind: "fake-records",
85
+ async plan(ctx: DeployContext, input: RecordsInput) {
86
+ w.recordsPlanned.push({ inputs: { ...input.inputs }, ...(ctx.unresolvedInputs ? { unresolved: [...ctx.unresolvedInputs] } : {}) });
87
+ const planDigest = computePlanDigest("fake-records", { inputs: input.inputs, unresolved: ctx.unresolvedInputs ?? [] });
88
+ const holes = ctx.unresolvedInputs?.length ? [{ address: "records", reason: `inputs from ${ctx.unresolvedInputs.join(", ")}; re-checked on apply` }] : [];
89
+ return {
90
+ member: "records",
91
+ planDigest,
92
+ artifact: { planDigest },
93
+ changeSet: { member: { member: "records", lexicon: "aws", planner: "chant", status: "planned", planDigest, holes }, entries: [] },
94
+ };
95
+ },
96
+ async run(_ctx: DeployContext, input: RecordsInput) {
97
+ w.recordsRan.push({ ...input.inputs });
98
+ return {};
99
+ },
100
+ },
101
+ ] as never,
102
+ engineOutputSources: () => [source],
103
+ };
104
+ // The registry the CLI builds: the starter set, whose member-outputs gets the plugins' sources bound.
105
+ const starter = { ...starterCapabilityPlugin, capabilities: () => starterCapabilityPlugin.capabilities().filter((c) => c.kind !== MEMBER_OUTPUTS_KIND).concat([memberOutputsCapability({ lookup }) as never]) };
106
+ const registry = registerCapabilityPlugins(new CapabilityRegistry(), [starter as CapabilityPlugin, plugin]);
107
+ return { registry, state: w };
108
+ }
109
+
110
+ const NOW = "2026-10-25T00:00:00Z";
111
+
112
+ describe("member-outputs", () => {
113
+ test("is in the starter set, and a registry built without a source for the member's kind refuses by name", async () => {
114
+ expect(starterCapabilityPlugin.capabilities().map((c) => c.kind)).toContain(MEMBER_OUTPUTS_KIND);
115
+ const bare = memberOutputsCapability({ lookup });
116
+ await expect(bare.outputs!({ env: "prod", component: "azure" }, { member: "azure" })).rejects.toBeInstanceOf(EngineOutputSourceMissingError);
117
+ await expect(bare.outputs!({ env: "prod", component: "azure" }, { member: "azure" })).rejects.toThrow(/member "azure" is kind choudoufu.*Add "terraform"/);
118
+ });
119
+
120
+ test("the component is a deploy unit named after the member", () => {
121
+ expect(deployUnits(memberOutputsComponent("azure").deploy)).toEqual([{ unit: "azure", lexicon: "workspace" }]);
122
+ });
123
+
124
+ test("an applied output feeds dns's parameter when the change does not touch the member", async () => {
125
+ const w = world();
126
+ const plan = planFanOut({ components: ESTATE, changed: ["records"] });
127
+ const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
128
+ expect(set.failed).toBe(false);
129
+ expect(w.state.recordsPlanned).toEqual([{ inputs: { azureEndpoint: "web.old.azurecontainerapps.io" } }]);
130
+ expect(set.deployedOutputs).toEqual({ azure: ["azureEndpoint"] });
131
+ expect(w.state.refs[0]).toEqual({ member: "azure", kind: "choudoufu", dir: "/ws/azure", workspaceRoot: "/ws" });
132
+ const records = set.members.find((m) => m.member === "records")!;
133
+ expect(records.unknownInputs).toBeUndefined();
134
+ expect(records.reads).toEqual([{ upstream: "azure", outputs: ["azureEndpoint"] }]);
135
+ });
136
+
137
+ test("a planned output the member's plan knows feeds dns's parameter when the change touches the member", async () => {
138
+ const w = world();
139
+ w.state.plan = { known: { azureEndpoint: "web.new.azurecontainerapps.io" }, unknown: [], planDigest: "sha256:azure-v2", changes: true };
140
+ const plan = planFanOut({ components: ESTATE, changed: ["azure"] });
141
+ const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
142
+ expect(w.state.recordsPlanned).toEqual([{ inputs: { azureEndpoint: "web.new.azurecontainerapps.io" } }]);
143
+ expect(set.plannedOutputs).toEqual({ azure: ["azureEndpoint"] });
144
+ const azure = set.doc.members.find((m) => m.member === memberOutputsMemberName("azure"))!;
145
+ expect(azure.holes).toEqual([]);
146
+ // The approval binds the member's plan: another plan of azure moves the set's digest.
147
+ const moved = world();
148
+ moved.state.plan = { ...w.state.plan, planDigest: "sha256:azure-v3" };
149
+ const again = await planPrSet({ components: ESTATE, plan, registry: moved.registry, env: "prod" });
150
+ expect(again.doc.digest).not.toBe(set.doc.digest);
151
+ });
152
+
153
+ test("an unknown upstream output holds dns back, naming the member and the output", async () => {
154
+ const w = world();
155
+ w.state.plan = { known: {}, unknown: ["azureEndpoint"], planDigest: "sha256:azure-new-app", changes: true };
156
+ const plan = planFanOut({ components: ESTATE, changed: ["azure"] });
157
+ const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
158
+ expect(set.failed).toBe(false);
159
+
160
+ // records was planned without the value, told whose output it waits on.
161
+ expect(w.state.recordsPlanned).toEqual([{ inputs: {}, unresolved: ["azure"] }]);
162
+ const records = set.members.find((m) => m.member === "records")!;
163
+ expect(records.unknownInputs).toEqual(["azure.azureEndpoint"]);
164
+ expect(records.reads).toEqual([{ upstream: "azure", outputs: ["azureEndpoint"], unknown: ["azureEndpoint"] }]);
165
+ expect(set.doc.members.find((m) => m.member === "records")!.holes).toEqual([{ address: "records", reason: "inputs from azure; re-checked on apply" }]);
166
+ // The member's own part names the output.
167
+ expect(set.doc.members.find((m) => m.member === memberOutputsMemberName("azure"))!.holes).toEqual([
168
+ { address: "azure.azureEndpoint", type: "output", reason: "azure.azureEndpoint is known only after azure applies; what reads it is planned again then" },
169
+ ]);
170
+
171
+ // On merge, before azure's own pipeline applied: the read refuses, so records is blocked behind it and nothing reads a stale value.
172
+ const early = await applyPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", set });
173
+ expect(early.members.find((m) => m.member === "records")).toMatchObject({ status: "blocked", blockedBy: "azure" });
174
+ expect(w.state.recordsRan).toEqual([]);
175
+
176
+ // Once azure applied: records is held back with the reason, and planned again against the value azure applied as.
177
+ w.state.applied = { azureEndpoint: "web.new.azurecontainerapps.io" };
178
+ w.state.plan = { known: { azureEndpoint: "web.new.azurecontainerapps.io" }, unknown: [], planDigest: "sha256:azure-applied", changes: false };
179
+ const decideNothing = async ({ gate, set: round }: { gate: string; set: PrPlanSet }) => {
180
+ const check = await decidePrGate(readOnlyLedger(memoryGateLedgerPort()), { op: "pr-12", gate, digest: round.doc.digest, now: NOW });
181
+ return { satisfied: check.satisfied, approval: approvalOf(check) };
182
+ };
183
+ const held = await applyPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", set, decideRound: decideNothing });
184
+ expect(held.status).toBe("waiting");
185
+ expect(held.members.find((m) => m.member === "records")).toMatchObject({
186
+ status: "waiting",
187
+ round: 2,
188
+ held: "its plan read azure.azureEndpoint before that output applied as another value",
189
+ });
190
+ expect(w.state.recordsPlanned.at(-1)).toEqual({ inputs: { azureEndpoint: "web.new.azurecontainerapps.io" } });
191
+ expect(w.state.recordsRan).toEqual([]);
192
+ });
193
+
194
+ test("run reads and never applies: a member with changes left refuses, one without answers its applied outputs", async () => {
195
+ const w = world();
196
+ const capability = w.registry.resolve(MEMBER_OUTPUTS_KIND);
197
+ w.state.plan = { ...w.state.plan, changes: true };
198
+ await expect(capability.run({ env: "prod", component: "azure" }, { member: "azure" } as never)).rejects.toBeInstanceOf(MemberOutputsPendingError);
199
+ w.state.plan = { ...w.state.plan, changes: false };
200
+ await expect(capability.run({ env: "prod", component: "azure" }, { member: "azure" } as never)).resolves.toEqual({
201
+ member: "azure",
202
+ outputs: { azureEndpoint: "web.old.azurecontainerapps.io" },
203
+ });
204
+ });
205
+ });
@@ -0,0 +1,308 @@
1
+ /**
2
+ * `member-outputs`: another workspace member's outputs, read through the
3
+ * engine that member runs on, so a chant member's parameters can be planned
4
+ * against them (#4455, design record choudoufu#2258 lane T6).
5
+ *
6
+ * In `behold/example-three-cloud`, `dns` (a chant member) reads each cloud's
7
+ * endpoint as a parameter of the same name as the output the cloud's member
8
+ * publishes. When the Azure site is a choudoufu estate, `azureEndpoint` is a
9
+ * Terraform root output, and when the AWS site is a Pulumi stack it is a
10
+ * stack output. Neither is a chant component, so there is nothing for a
11
+ * `stackOutput()` reference to read. This verb is that something: a
12
+ * component in the consumer's project whose one step names the upstream
13
+ * member, and whose capability answers the two questions the components
14
+ * machinery already asks of an upstream:
15
+ *
16
+ * - `outputs` (applied): what the member exposes now. A pull request that
17
+ * does not touch the member plans the consumer against these
18
+ * (`deployedOutputs`, #4104).
19
+ * - `plan` (planned): what the member will expose once its own plan applies,
20
+ * with the outputs known only after apply named (`PlannedOutputs`, #3999).
21
+ * A pull request that touches the member plans the consumer against the
22
+ * known ones. A reference to an unknown one is unresolved, so the
23
+ * consumer's step is planned with `DeployContext.unresolvedInputs` naming
24
+ * the member, the pull request's report lists `<member>.<output>` under the
25
+ * consumer's `unknownInputs`, and the apply holds the consumer back until
26
+ * the member applied, then plans it again (`pr-run.ts`, "Planning again
27
+ * once an upstream applied"). That is the hold chant already puts on a
28
+ * root waiting on another root's unknown output; nothing here adds a
29
+ * second one.
30
+ *
31
+ * The consumer writes it as two components:
32
+ *
33
+ * ```ts
34
+ * export const azure = memberOutputsComponent("azure");
35
+ * export const records: Component = {
36
+ * name: "records",
37
+ * dependsOn: ["azure"],
38
+ * deploy: [phase("Apply", [{ kind: "cfn-deploy", template: "dist/dns.json",
39
+ * inputs: { azureEndpoint: stackOutput("azure", "azureEndpoint") } }])],
40
+ * };
41
+ * ```
42
+ *
43
+ * ## Engines: the seam
44
+ *
45
+ * The member is looked up in the workspace declaration (`chant.workspace.json`)
46
+ * by name, and its `kind` picks the {@link EngineOutputSource} that reads it.
47
+ * Sources are not capabilities: each lexicon that drives an engine
48
+ * contributes one through `CapabilityPlugin.engineOutputSources()`, the way
49
+ * the cloud lexicons contribute registry logins to `publish-image`, and the
50
+ * registry binds them into this one verb when it is built
51
+ * (`registerCapabilityPlugins`). So two engines never register the same
52
+ * kind, and a consumer's step does not change when the member moves between
53
+ * engines.
54
+ *
55
+ * - `choudoufu` (the terraform lexicon, `choudoufuOutputSource`): applied
56
+ * values from `choudoufu output -json` in the member's directory, which in
57
+ * live mode reads the estate's record store; planned values from
58
+ * `live-plan-set -json` over the member's root, its plan's
59
+ * `planned_values.outputs` with every output whose `output_changes` entry
60
+ * marks any part `after_unknown` named unknown (`terraformPlannedOutputs`).
61
+ * - `pulumi` (the pulumi lexicon, `pulumiOutputSource`): applied values
62
+ * from `pulumi stack output --json --show-secrets=false`, or from the
63
+ * member's checked-in `stack-export.json` when chant must not run pulumi
64
+ * (`CHANT_PULUMI_APPLIED_OUTPUTS=export`); planned values from `pulumi
65
+ * preview --json`, the stack step's outputs, where an output holding
66
+ * Pulumi's unknown sentinel is unknown. Secret outputs are left out of
67
+ * both. A member on plunge runs the same commands under `plunge`.
68
+ * Nothing in this module or in the consumer changes for it.
69
+ *
70
+ * ## Run reads, it never applies
71
+ *
72
+ * The member applies through its own pipeline. `run` checks that the
73
+ * member's plan has nothing left to apply (`changes` false) and answers its
74
+ * applied outputs; a member with changes still to apply refuses the run
75
+ * with {@link MemberOutputsPendingError}, so the consumer is blocked behind
76
+ * it and never applies against values from before the member's change. A
77
+ * workspace orders the member's own run before the consumer's (members that
78
+ * link to a producer run after it, `chant workspace run`).
79
+ *
80
+ * ## Which changes reach the member
81
+ *
82
+ * The component is a deploy unit named after the member (`DEPLOY_UNIT_RULES`),
83
+ * so a change signal that names the member as a changed unit puts it in the
84
+ * change. The terraform lexicon names it when the consumer's
85
+ * `terraform.roots` has an entry of that name whose files changed;
86
+ * `--from-affected` can name it too.
87
+ */
88
+
89
+ import type { Capability, CapabilityPlan, DeployContext, PlannedOutputs } from "../capability";
90
+ import type { ChangeSetHole, ChangeSetPart } from "../../change-set";
91
+ import { computePlanDigest } from "../../lifecycle/plan-digest";
92
+ import type { Component } from "../component";
93
+
94
+ /** The step kind. */
95
+ export const MEMBER_OUTPUTS_KIND = "member-outputs";
96
+
97
+ /** The `member-outputs` step's input. */
98
+ export interface MemberOutputsInput {
99
+ /** The upstream member's name in the workspace declaration. Also the component's name, by convention. */
100
+ member: string;
101
+ /**
102
+ * Where the declaration search starts. Default: the current directory,
103
+ * which is the consumer's project root under `chant run`.
104
+ */
105
+ cwd?: string;
106
+ }
107
+
108
+ /** What `run` returns: the member's applied outputs, under `outputs`, where the driver collects a stack's outputs from. */
109
+ export interface MemberOutputsOutput {
110
+ member: string;
111
+ outputs: Record<string, unknown>;
112
+ }
113
+
114
+ /** The upstream member a source reads, as the workspace declares it. */
115
+ export interface EngineMemberRef {
116
+ /** The member's name. */
117
+ member: string;
118
+ /** Its declared kind, which picked the source: `choudoufu`, `pulumi`. */
119
+ kind: string;
120
+ /** Its directory, absolute. */
121
+ dir: string;
122
+ /** The workspace root, absolute. A source that must run from above the member's directory runs from here. */
123
+ workspaceRoot: string;
124
+ }
125
+
126
+ /** What a source's plan of the member says about its outputs. */
127
+ export interface EnginePlannedOutputs extends PlannedOutputs {
128
+ /**
129
+ * A digest of the member's plan, the engine's own or chant's over it. The
130
+ * consumer's approval binds it, so a plan of the member that moves after
131
+ * review moves the consumer's digest too.
132
+ */
133
+ planDigest: string;
134
+ /** Whether the member's plan has anything to apply. `run` refuses while it does. */
135
+ changes: boolean;
136
+ }
137
+
138
+ /**
139
+ * Reads one engine's member outputs (#4455). One per engine, contributed by
140
+ * the lexicon that drives it through `CapabilityPlugin.engineOutputSources()`.
141
+ */
142
+ export interface EngineOutputSource {
143
+ /** The member kinds this source reads, as declared in `chant.workspace.json`. */
144
+ readonly kinds: readonly string[];
145
+ /** The member's outputs as applied now, by name. */
146
+ applied(ctx: DeployContext, ref: EngineMemberRef): Promise<Record<string, unknown>>;
147
+ /** The member's outputs once its current plan applies, the ones known only after apply named. */
148
+ planned(ctx: DeployContext, ref: EngineMemberRef): Promise<EnginePlannedOutputs>;
149
+ }
150
+
151
+ /** Finds a member in the workspace above `cwd`: its kind and directory. */
152
+ export type MemberLookup = (cwd: string, member: string) => Promise<EngineMemberRef>;
153
+
154
+ /** No source for the member's kind is loaded. */
155
+ export class EngineOutputSourceMissingError extends Error {
156
+ constructor(
157
+ readonly member: string,
158
+ readonly kind: string,
159
+ readonly available: readonly string[],
160
+ ) {
161
+ super(
162
+ `${MEMBER_OUTPUTS_KIND}: member "${member}" is kind ${kind}, and no loaded lexicon reads that engine's outputs` +
163
+ (available.length > 0 ? ` (loaded: ${available.join(", ")})` : "") +
164
+ `. ${kind === "choudoufu" ? 'Add "terraform" to the project\'s lexicons.' : kind === "pulumi" ? 'Add "pulumi" to the project\'s lexicons.' : "Load the lexicon that drives it."}`,
165
+ );
166
+ this.name = "EngineOutputSourceMissingError";
167
+ }
168
+ }
169
+
170
+ /** The member's plan has changes its own pipeline has not applied. Nothing was read. */
171
+ export class MemberOutputsPendingError extends Error {
172
+ constructor(readonly member: string) {
173
+ super(
174
+ `${MEMBER_OUTPUTS_KIND}: member "${member}" has changes its own pipeline has not applied yet, ` +
175
+ `so its outputs are still the ones from before them. Apply ${member} first; whatever reads its outputs runs after it.`,
176
+ );
177
+ this.name = "MemberOutputsPendingError";
178
+ }
179
+ }
180
+
181
+ /** The member a {@link MemberOutputsInput} reads plans as, in the consumer's change set. */
182
+ export const memberOutputsMemberName = (member: string): string => `${member}#outputs`;
183
+
184
+ /**
185
+ * The member's name in the declaration above `cwd`, with its kind and
186
+ * directory. Reads the working tree. Imported lazily: the verb set is
187
+ * loaded where the workspace reader's dependencies may not be.
188
+ */
189
+ export const workspaceMemberLookup: MemberLookup = async (cwd, member) => {
190
+ const { locateWorkspace } = await import("../../workspace/which-chant");
191
+ const { readDeclaration } = await import("../../workspace/declaration");
192
+ const { join } = await import("node:path");
193
+ const located = locateWorkspace(cwd);
194
+ const declaration = readDeclaration(located.tree);
195
+ const found = declaration.members.find((m) => m.name === member);
196
+ if (!found) {
197
+ const names = declaration.members.map((m) => m.name).sort();
198
+ throw new Error(`${MEMBER_OUTPUTS_KIND}: the workspace at ${located.rootOnDisk} declares no member "${member}" (members: ${names.join(", ")})`);
199
+ }
200
+ return {
201
+ member: found.name,
202
+ kind: found.kind,
203
+ dir: found.dir === "." ? located.rootOnDisk : join(located.rootOnDisk, ...found.dir.split("/")),
204
+ workspaceRoot: located.rootOnDisk,
205
+ };
206
+ };
207
+
208
+ function requireMember(input: MemberOutputsInput): void {
209
+ if (typeof input?.member !== "string" || input.member === "") {
210
+ throw new Error(`${MEMBER_OUTPUTS_KIND}: "member" is required and names a member of the workspace`);
211
+ }
212
+ }
213
+
214
+ /** Implemented by a capability that wants the plugins' engine output sources injected when a registry is built. */
215
+ export interface EngineOutputSourceConsumer {
216
+ withEngineOutputSources(sources: readonly EngineOutputSource[]): Capability<never, unknown>;
217
+ }
218
+
219
+ export function isEngineOutputSourceConsumer(value: unknown): value is EngineOutputSourceConsumer {
220
+ return typeof value === "object" && value !== null && typeof (value as EngineOutputSourceConsumer).withEngineOutputSources === "function";
221
+ }
222
+
223
+ /**
224
+ * The member's part of the consumer's change set: no entries, since the
225
+ * member's own pipeline plans and applies its changes, and a hole per output
226
+ * known only after apply, naming the member and the output.
227
+ */
228
+ function outputsPart(member: string, planDigest: string, unknown: readonly string[]): ChangeSetPart {
229
+ const holes: ChangeSetHole[] = unknown.map((name) => ({
230
+ address: `${member}.${name}`,
231
+ type: "output",
232
+ reason: `${member}.${name} is known only after ${member} applies; what reads it is planned again then`,
233
+ }));
234
+ return {
235
+ member: { member: memberOutputsMemberName(member), lexicon: "chant", planner: "chant", status: "planned", planDigest, holes },
236
+ entries: [],
237
+ };
238
+ }
239
+
240
+ export interface MemberOutputsCapabilityOptions {
241
+ /** The engines' sources. Bound by the registry from the loaded plugins; empty until then. */
242
+ sources?: readonly EngineOutputSource[];
243
+ /** How a member is found. Default: {@link workspaceMemberLookup}. */
244
+ lookup?: MemberLookup;
245
+ }
246
+
247
+ /** `member-outputs` over the given sources. */
248
+ export function memberOutputsCapability(
249
+ options: MemberOutputsCapabilityOptions = {},
250
+ ): Capability<MemberOutputsInput, MemberOutputsOutput> & EngineOutputSourceConsumer {
251
+ const sources = options.sources ?? [];
252
+ const lookup = options.lookup ?? workspaceMemberLookup;
253
+
254
+ async function resolve(input: MemberOutputsInput): Promise<{ ref: EngineMemberRef; source: EngineOutputSource }> {
255
+ requireMember(input);
256
+ const ref = await lookup(input.cwd ?? process.cwd(), input.member);
257
+ const source = sources.find((s) => s.kinds.includes(ref.kind));
258
+ if (!source) throw new EngineOutputSourceMissingError(ref.member, ref.kind, [...new Set(sources.flatMap((s) => s.kinds))].sort());
259
+ return { ref, source };
260
+ }
261
+
262
+ return {
263
+ kind: MEMBER_OUTPUTS_KIND,
264
+ // It reads; the member's own pipeline applies. Nothing to compensate.
265
+ rollbackPolicy: "none-by-design",
266
+ targets: { refuse: "it reads another member's outputs; narrow that member's own plan instead" },
267
+ async plan(ctx: DeployContext, input: MemberOutputsInput): Promise<CapabilityPlan> {
268
+ const { ref, source } = await resolve(input);
269
+ const planned = await source.planned(ctx, ref);
270
+ const unknown = [...new Set(planned.unknown)].sort();
271
+ const known = Object.fromEntries(Object.entries(planned.known).filter(([name]) => !unknown.includes(name)));
272
+ const member = memberOutputsMemberName(ref.member);
273
+ const planDigest = computePlanDigest(MEMBER_OUTPUTS_KIND, { env: ctx.env, member: ref.member, kind: ref.kind, plan: planned.planDigest, known, unknown });
274
+ return { member, planDigest, changeSet: outputsPart(ref.member, planDigest, unknown), outputs: { known, unknown } };
275
+ },
276
+ async outputs(ctx: DeployContext, input: MemberOutputsInput): Promise<Record<string, unknown>> {
277
+ const { ref, source } = await resolve(input);
278
+ return source.applied(ctx, ref);
279
+ },
280
+ async run(ctx: DeployContext, input: MemberOutputsInput): Promise<MemberOutputsOutput> {
281
+ const { ref, source } = await resolve(input);
282
+ const now = await source.planned(ctx, ref);
283
+ if (now.changes) throw new MemberOutputsPendingError(ref.member);
284
+ return { member: ref.member, outputs: await source.applied(ctx, ref) };
285
+ },
286
+ withEngineOutputSources(next: readonly EngineOutputSource[]) {
287
+ return memberOutputsCapability({ ...options, sources: next }) as unknown as Capability<never, unknown>;
288
+ },
289
+ };
290
+ }
291
+
292
+ /** The starter set's `member-outputs`, with no sources until a registry binds the loaded plugins' ones. */
293
+ export const memberOutputsStarterCapability = memberOutputsCapability();
294
+
295
+ /**
296
+ * A component in the consumer's project that stands for workspace member
297
+ * `member` (#4455): one step reading its outputs. Named after the member, so
298
+ * `stackOutput(member, "<output>")` reads them and `dependsOn: [member]`
299
+ * orders the consumer after it.
300
+ */
301
+ export function memberOutputsComponent(member: string, options: { cwd?: string } = {}): Component {
302
+ return {
303
+ name: member,
304
+ archetype: "infra",
305
+ dependsOn: [],
306
+ deploy: [{ phase: "Outputs", steps: [{ kind: MEMBER_OUTPUTS_KIND, member, ...(options.cwd ? { cwd: options.cwd } : {}) }] }],
307
+ };
308
+ }