@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
@@ -67,6 +67,14 @@
67
67
  * says whether the approved change set covers what it planned, by the same
68
68
  * test a resume uses ({@link planCoveredBy}). Covered, the wave applies under
69
69
  * the pull request's approval; not covered, it needs its own.
70
+ *
71
+ * ## Groups
72
+ *
73
+ * A fan-out with `groups` (ws-107, `./groups.ts`) plans each group with one
74
+ * `planGroup` call at its wave and applies it with one `runGroup` call
75
+ * there. A member's read of another member of its group is computed by that
76
+ * apply: it is no `unknownInputs` entry and holds nothing back. Each member's
77
+ * digest binds the group's, so the one approval binds the composition.
70
78
  */
71
79
 
72
80
  import { composeChangeSet, verifyChangeSetDigest, type ChangeSetDocument, type ChangeSetEntry, type ChangeSetPart } from "../change-set";
@@ -78,6 +86,7 @@ import {
78
86
  PR_REPORT_CONTRACT,
79
87
  PR_REPORT_SCHEMA_ID,
80
88
  type PrApproval,
89
+ type PrGroup,
81
90
  type PrMember,
82
91
  type PrMemberRead,
83
92
  type PrRefusal,
@@ -96,13 +105,14 @@ import {
96
105
  } from "./driver";
97
106
  import { deployUnits } from "./deploy-units";
98
107
  import { runFanOut, type FanOutRunResult } from "./fan-out-run";
99
- import { remainingFanOut, type ComponentChangeSignal, type FanOutPlan } from "./fan-out";
108
+ import { remainingFanOut, type ComponentChangeSignal, type FanOutGroup, type FanOutGroupFallback, type FanOutPlan } from "./fan-out";
100
109
  import { coveredRound, type PlanShape, type RoundCoverage } from "./covered-round";
101
110
  import type { PrApplyRecord } from "./fan-out-record";
102
- import type { CapabilityRegistry, PlannedOutputs } from "./capability";
111
+ import type { CapabilityRegistry, GroupRun, PlannedOutputs } from "./capability";
112
+ import { groupsByComponent, narrowGroups, planComponentGroup, regroupRemaining } from "./groups";
103
113
  import type { RunProgressEvent } from "./run-progress";
104
114
  import type { RunEventSink } from "../run-event-stream";
105
- import { capabilitySteps, planWaveComponent, readDeployedOutputs } from "./wave-plan";
115
+ import { capabilitySteps, planWaveComponent, readDeployedOutputs, type WaveComponentPlan } from "./wave-plan";
106
116
  import type { PrMigrationFile } from "../pr-migrations";
107
117
 
108
118
  export interface PrPlanSetOptions {
@@ -151,10 +161,24 @@ export interface PrPlanSet {
151
161
  plannedOutputs?: Record<string, string[]>;
152
162
  /** The shapes lexicons supplied with their plans (#4419), by member. */
153
163
  shapes?: Record<string, PlanShape>;
164
+ /** The groups planned as one (ws-107), in plan order of their first member. Absent when none. */
165
+ groups?: PrPlanGroup[];
166
+ /** The components that could have grouped and did not, with why (ws-107, ruling 1): the fan-out's `fallbacks`. */
167
+ fallbacks?: FanOutGroupFallback[];
154
168
  /** Whether any component failed to plan. */
155
169
  failed: boolean;
156
170
  }
157
171
 
172
+ /** A group a pull request planned as one (ws-107). */
173
+ export interface PrPlanGroup extends FanOutGroup {
174
+ /** The group digest folded into each member's `planDigest`; `null` when the group failed to plan. */
175
+ digest: string | null;
176
+ /** Why it failed to plan. Every member is then plan-failed with it. */
177
+ error?: string;
178
+ /** What `runGroup` gets back. Like `plans`, it lives in the process that planned. */
179
+ run?: GroupRun;
180
+ }
181
+
158
182
  /** A failed component's members: the units its steps target, else the component itself. */
159
183
  function failedParts(component: DriverComponent, error: string): ChangeSetPart[] {
160
184
  const units = deployUnits(component.deploy).map((u) => u.unit);
@@ -190,14 +214,19 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
190
214
  let failed = false;
191
215
  const emit = options.onRunEvent;
192
216
  emit?.({ type: "phase-start", component: null, phase: PR_PLAN_PHASE });
217
+ // ws-107: each group plans once, in its wave, with one call.
218
+ const groupOf = groupsByComponent(options.plan);
219
+ const groups: PrPlanGroup[] = [];
193
220
 
194
221
  for (const wave of options.plan.waves) {
195
222
  // The dependencies' outputs first, each once. One the change plans is
196
223
  // read from its plan, which an earlier wave made (#3999). Any other is
197
224
  // read from the deployed estate (#4104). A dependency that cannot say
198
225
  // leaves its references unresolved, which its dependent's plan reports
199
- // if it needs them.
200
- const deps = [...new Set(wave.flatMap((name) => byName.get(name)?.dependsOn ?? []))].filter((dep) => !read.has(dep));
226
+ // if it needs them. A member of the dependent's own group plans with it.
227
+ const deps = [
228
+ ...new Set(wave.flatMap((name) => (byName.get(name)?.dependsOn ?? []).filter((dep) => !groupOf.get(name)?.components.includes(dep)))),
229
+ ].filter((dep) => !read.has(dep));
201
230
  const fromEstate: string[] = [];
202
231
  for (const dep of deps) {
203
232
  read.add(dep);
@@ -211,27 +240,59 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
211
240
  planned[dep] = Object.keys(known).sort();
212
241
  }
213
242
  Object.assign(deployed, await readDeployedOutputs(fromEstate, byName, options.registry, (dep) => deployContext(options, dep), outputs));
214
- const results = await Promise.all(
215
- wave.map(async (name) => {
216
- const at = { component: name, phase: PR_PLAN_PHASE, step: "plan" };
217
- emit?.({ type: "step-start", ...at });
218
- const start = Date.now();
219
- const p = await planWaveComponent(byName.get(name)!, options.registry, deployContext(options, name), outputs);
220
- emit?.({
221
- type: "step-settle",
222
- ...at,
223
- status: "error" in p ? "failed" : "ok",
224
- durationMs: Date.now() - start,
225
- ...("error" in p ? { error: p.error } : {}),
226
- });
227
- return p;
228
- }),
229
- );
243
+ const settle = (name: string, start: number, p: WaveComponentPlan): void => {
244
+ emit?.({
245
+ type: "step-settle",
246
+ component: name,
247
+ phase: PR_PLAN_PHASE,
248
+ step: "plan",
249
+ status: "error" in p ? "failed" : "ok",
250
+ durationMs: Date.now() - start,
251
+ ...("error" in p ? { error: p.error } : {}),
252
+ });
253
+ };
254
+ const groupsHere = [...new Set(wave.map((name) => groupOf.get(name)).filter((g): g is FanOutGroup => g !== undefined))];
255
+ const results = (
256
+ await Promise.all([
257
+ ...wave
258
+ .filter((name) => !groupOf.has(name))
259
+ .map(async (name) => {
260
+ emit?.({ type: "step-start", component: name, phase: PR_PLAN_PHASE, step: "plan" });
261
+ const start = Date.now();
262
+ const p = await planWaveComponent(byName.get(name)!, options.registry, deployContext(options, name), outputs);
263
+ settle(name, start, p);
264
+ return [p];
265
+ }),
266
+ ...groupsHere.map(async (group) => {
267
+ for (const name of group.components) emit?.({ type: "step-start", component: name, phase: PR_PLAN_PHASE, step: "plan" });
268
+ const start = Date.now();
269
+ const g = await planComponentGroup({
270
+ group,
271
+ components: byName,
272
+ registry: options.registry,
273
+ env: options.env,
274
+ ...(options.vars ? { vars: options.vars } : {}),
275
+ ctxOf: (name) => deployContext(options, name),
276
+ outputs,
277
+ });
278
+ for (const p of g.plans) settle(p.component, start, p);
279
+ const error = g.plans.find((p): p is Extract<WaveComponentPlan, { error: string }> => "error" in p)?.error;
280
+ groups.push({ ...group, digest: g.digest ?? null, ...(error !== undefined ? { error } : {}), ...(g.run ? { run: g.run } : {}) });
281
+ return g.plans;
282
+ }),
283
+ ])
284
+ ).flat();
285
+ /** What each member of a group planned to expose, for its group-mates' reads. */
286
+ const plannedHere = new Map(results.map((p) => [p.component, "error" in p ? undefined : p.outputs?.known]));
230
287
  for (const p of results) {
231
288
  const component = byName.get(p.component)!;
232
- const unknown = unknownInputsOf(component, outputs, inChange);
289
+ const group = groupOf.get(p.component);
290
+ const mates = group ? new Set(group.components) : undefined;
291
+ const unknown = unknownInputsOf(component, outputs, inChange, mates);
233
292
  if (unknown.unknown.length > 0 || unknown.build.length > 0) unknownOf.set(p.component, unknown);
234
- const reads = readsOf(component, outputs);
293
+ // A read of a group-mate is known when that mate's plan knows it.
294
+ const readsView = group ? { ...outputs, ...Object.fromEntries(group.components.filter((c) => c !== p.component).map((c) => [c, { ...(plannedHere.get(c) ?? {}) }])) } : outputs;
295
+ const reads = readsOf(component, readsView);
235
296
  if (reads.length > 0) readsBy.set(p.component, reads);
236
297
  if ("error" in p) {
237
298
  failed = true;
@@ -273,12 +334,16 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
273
334
  const sorted = (r: Record<string, string[]>) => Object.fromEntries(Object.entries(r).sort(([a], [b]) => a.localeCompare(b)));
274
335
  const deployedOutputs = sorted(deployed);
275
336
  const plannedOutputs = sorted(planned);
337
+ const at = new Map(options.plan.order.map((name, i) => [name, i]));
338
+ groups.sort((a, b) => (at.get(a.components[0]!) ?? 0) - (at.get(b.components[0]!) ?? 0));
276
339
  return {
277
340
  doc,
278
341
  members,
279
342
  plans,
280
343
  outputs,
281
344
  failed,
345
+ ...(groups.length > 0 ? { groups } : {}),
346
+ ...(options.plan.fallbacks?.length ? { fallbacks: options.plan.fallbacks } : {}),
282
347
  ...(Object.keys(shapes).length > 0 ? { shapes } : {}),
283
348
  ...(Object.keys(deployedOutputs).length > 0 ? { deployedOutputs } : {}),
284
349
  ...(Object.keys(plannedOutputs).length > 0 ? { plannedOutputs } : {}),
@@ -291,18 +356,21 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
291
356
  * that upstream applies, each sorted. `build` are the published artifact
292
357
  * identities a build in the change produces (`WiringReference.publish`):
293
358
  * the build's provenance binds those, so they are never approved again.
294
- * `unknown` are the rest.
359
+ * `unknown` are the rest. A reference to a member of `group`, the
360
+ * component's own group (ws-107), is neither: the group's apply computes it.
295
361
  */
296
362
  function unknownInputsOf(
297
363
  component: DriverComponent,
298
364
  outputs: Record<string, Record<string, unknown>>,
299
365
  inChange: ReadonlySet<string>,
366
+ group?: ReadonlySet<string>,
300
367
  ): { unknown: string[]; build: string[] } {
301
368
  const unknown = new Set<string>();
302
369
  const build = new Set<string>();
303
370
  for (const step of capabilitySteps(component.deploy)) {
304
371
  const { kind: _kind, ...rest } = step;
305
372
  for (const r of unresolvedWiringReferences(rest, outputs)) {
373
+ if (group?.has(r.source)) continue;
306
374
  if (inChange.has(r.source)) (r.publish ? build : unknown).add(r.ref);
307
375
  }
308
376
  }
@@ -339,18 +407,21 @@ function readsOf(component: DriverComponent, outputs: Record<string, Record<stri
339
407
  * stands (#3999): each reference its steps make that resolves to another
340
408
  * value, said once. `undefined` when every one resolves as it did. A
341
409
  * published artifact's identity from a build in `inChange` never holds a
342
- * member back: the build's provenance binds it, not this approval.
410
+ * member back: the build's provenance binds it, not this approval. Nor
411
+ * does a reference to a member of `group`, the component's own group
412
+ * (ws-107), whose apply computes it.
343
413
  */
344
414
  export function heldReason(
345
415
  component: DriverComponent,
346
416
  read: Record<string, Record<string, unknown>>,
347
417
  now: Record<string, Record<string, unknown>>,
348
418
  inChange: ReadonlySet<string> = new Set(),
419
+ group: ReadonlySet<string> = new Set(),
349
420
  ): string | undefined {
350
421
  const refs = new Set<string>();
351
422
  for (const step of capabilitySteps(component.deploy)) {
352
423
  const { kind: _kind, ...rest } = step;
353
- for (const r of movedWiringReferences(rest, read, now)) if (!(r.publish && inChange.has(r.source))) refs.add(r.ref);
424
+ for (const r of movedWiringReferences(rest, read, now)) if (!(r.publish && inChange.has(r.source)) && !group.has(r.source)) refs.add(r.ref);
354
425
  }
355
426
  if (refs.size === 0) return undefined;
356
427
  return `its plan read ${[...refs].sort().join(", ")} before ${refs.size === 1 ? "that output" : "those outputs"} applied as another value`;
@@ -494,6 +565,9 @@ function roundCover(
494
565
  byName: ReadonlyMap<string, DriverComponent>,
495
566
  ): RoundCoverage {
496
567
  if (options.approval?.status !== "approved") return { covered: false, reason: "no approval of the first plan stands" };
568
+ // ws-107: a round's group is covered only when it has the approved group's members.
569
+ const moved = groupsMoved(options.set.groups ?? [], fresh.groups ?? [], plan.order);
570
+ if (moved) return { covered: false, reason: moved };
497
571
  // The values the round reads of a component it plans again are the ones
498
572
  // that plan says it will expose: it has not applied yet, so the outputs
499
573
  // standing say nothing of it. An output the first plan knew and the round's
@@ -512,16 +586,45 @@ function roundCover(
512
586
  }
513
587
  changedKnown[name] = [...refs].sort();
514
588
  }
589
+ // A member whose round plan still reads only outputs of components this
590
+ // round plans and has not applied (a chain of imports, #4466d) cannot apply
591
+ // in this round: it is held again once those apply, and its own round
592
+ // checks its coverage. Only the members this round can apply are checked.
593
+ const inRound = plan.order;
594
+ const fromRound = (ref: string): boolean => inRound.some((c) => ref === c || ref.startsWith(`${c}.`));
595
+ const waits = (m: { unknownInputs?: string[] }): boolean => (m.unknownInputs?.length ?? 0) > 0 && m.unknownInputs!.every(fromRound);
515
596
  const approved = { doc: options.set.doc, members: options.set.members, ...(options.set.shapes ? { shapes: options.set.shapes } : {}) };
516
597
  return coveredRound({
517
598
  approved,
518
599
  approvedDigest: options.set.doc.digest,
519
- fresh: { doc: fresh.doc, members: fresh.members, ...(fresh.shapes ? { shapes: fresh.shapes } : {}) },
600
+ fresh: { doc: fresh.doc, members: fresh.members.filter((m) => !waits(m)), ...(fresh.shapes ? { shapes: fresh.shapes } : {}) },
520
601
  changedKnown,
521
602
  attributes: (member) => memberAttributes(options.set.doc, fresh.doc, member, fresh.shapes?.[member] !== undefined),
522
603
  });
523
604
  }
524
605
 
606
+ /**
607
+ * Why the groups `fresh` planned are not the `approved` ones over the
608
+ * components in `order` (ws-107), or `undefined` when each group the round
609
+ * plans has the members the approved group had.
610
+ */
611
+ function groupsMoved(approved: readonly FanOutGroup[], fresh: readonly FanOutGroup[], order: readonly string[]): string | undefined {
612
+ const same = (a: readonly string[], b: readonly string[]): boolean => a.length === b.length && a.every((c) => b.includes(c));
613
+ const inRound = new Set(order);
614
+ for (const was of approved) {
615
+ const here = was.components.filter((c) => inRound.has(c));
616
+ if (here.length === 0) continue;
617
+ const now = fresh.find((g) => g.components.includes(here[0]!));
618
+ if (!now || !same(now.components, was.components)) {
619
+ return `the approved plan grouped ${was.components.join(", ")}, and the round ${now ? `groups ${now.components.join(", ")}` : `plans ${here.join(", ")} without that group`}`;
620
+ }
621
+ }
622
+ for (const now of fresh) {
623
+ if (!approved.some((was) => same(was.components, now.components))) return `the round groups ${now.components.join(", ")}, which the approved plan did not`;
624
+ }
625
+ return undefined;
626
+ }
627
+
525
628
  /**
526
629
  * The part of coverage that reads the change set's own attributes, shared by
527
630
  * a round, a resume and a wave: a member whose planner describes no changes
@@ -553,6 +656,8 @@ function joinRuns(a: FanOutRunResult, b: FanOutRunResult): FanOutRunResult {
553
656
  componentOutputs: b.componentOutputs,
554
657
  ...(gate ? { gate } : {}),
555
658
  ...(b.held ? { held: b.held } : {}),
659
+ ...(a.notStarted || b.notStarted ? { notStarted: [...new Set([...(a.notStarted ?? []), ...(b.notStarted ?? [])])].sort() } : {}),
660
+ ...(a.groupOutcomes || b.groupOutcomes ? { groupOutcomes: { ...(a.groupOutcomes ?? {}), ...(b.groupOutcomes ?? {}) } } : {}),
556
661
  };
557
662
  }
558
663
 
@@ -576,20 +681,25 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
576
681
  ...Object.fromEntries(Object.entries(options.set.outputs).filter(([name]) => !toApply.has(name))),
577
682
  ...(options.resumed?.outputs ?? {}),
578
683
  };
579
- const runRound = (plan: FanOutPlan, plans: Record<string, Record<string, unknown>>, read: Record<string, Record<string, unknown>>, outputs: Record<string, Record<string, unknown>>) =>
580
- runFanOut(plan, options.components, options.registry, {
684
+ const runRound = (plan: FanOutPlan, set: Pick<PrPlanSet, "plans" | "groups">, read: Record<string, Record<string, unknown>>, outputs: Record<string, Record<string, unknown>>) => {
685
+ // ws-107: each group runs the plan its group planned, and a read of a group-mate holds nothing back.
686
+ const groupOf = groupsByComponent(plan);
687
+ const groupRuns = Object.fromEntries((set.groups ?? []).filter((g) => g.run).map((g) => [g.components[0]!, g.run!]));
688
+ return runFanOut(plan, options.components, options.registry, {
581
689
  env: options.env,
582
690
  ...(options.vars ? { vars: options.vars } : {}),
583
- plans,
691
+ plans: set.plans,
692
+ ...(Object.keys(groupRuns).length > 0 ? { groupRuns } : {}),
584
693
  ...(options.targets ? { targets: options.targets } : {}),
585
694
  componentOutputs: outputs,
586
- hold: (name, now) => heldReason(byName.get(name)!, read, now, inChange),
695
+ hold: (name, now) => heldReason(byName.get(name)!, read, now, inChange, new Set(groupOf.get(name)?.components ?? [])),
587
696
  ...(options.onProgress ? { onProgress: options.onProgress } : {}),
588
697
  ...(options.onRunEvent ? { onRunEvent: options.onRunEvent } : {}),
589
698
  ...(options.onComponentSettled ? { onComponentSettled: options.onComponentSettled } : {}),
590
699
  });
700
+ };
591
701
 
592
- let run = await runRound(options.plan, options.set.plans, options.set.outputs, { ...standing });
702
+ let run = await runRound(options.plan, options.set, options.set.outputs, { ...standing });
593
703
  const rounds: PrRound[] = [];
594
704
  /** Per component, the round that applied it or holds it, and that round's set, after the first. */
595
705
  const roundOf = new Map<string, { round: number; set: PrPlanSet }>();
@@ -598,7 +708,8 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
598
708
  const round = rounds.length + 2;
599
709
  const gate = prRoundGate(options.gate ?? PR_APPLY_GATE, round);
600
710
  const heldNames = new Set(run.held.map((h) => h.component));
601
- const plan = remainingFanOut(options.plan, options.components, { completed: options.plan.order.filter((name) => !heldNames.has(name)) });
711
+ // A held group is planned again as the same group (ws-107).
712
+ const plan = narrowGroups(remainingFanOut(options.plan, options.components, { completed: options.plan.order.filter((name) => !heldNames.has(name)) }), options.components, options.plan.groups);
602
713
  const set = await planPrSet({
603
714
  components: options.components,
604
715
  plan,
@@ -638,25 +749,33 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
638
749
  stopped = "waiting";
639
750
  break;
640
751
  }
641
- const next = await runRound(plan, set.plans, set.outputs, run.componentOutputs);
752
+ const next = await runRound(plan, set, set.outputs, run.componentOutputs);
642
753
  record(next.status === "fail" ? "failed" : "applied", decision.approval, undefined, decision.covered);
643
754
  run = joinRuns(run, next);
644
755
  }
645
756
 
646
757
  const completed = new Set(run.completed);
758
+ const notStarted = new Set(run.notStarted ?? []);
647
759
  const held = new Map((run.held ?? []).map((h) => [h.component, h]));
648
760
  const blocked = new Map(run.blocked.map((b) => [b.component, b.blockedBy]));
649
761
  const errorOf = (component: string): string | undefined => {
650
762
  const result = [...run.results].reverse().find((r) => r.component === component);
651
763
  if (result?.status === "gated") return `stopped at its own gate ${result.gate?.gate ?? ""}`.trim();
652
- return result?.records.find((r) => r.status === "fail")?.error;
764
+ return result?.records.find((r) => r.status === "fail" || (notStarted.has(component) && r.status === "skipped"))?.error;
653
765
  };
654
766
  const members = options.set.members.map((m): PrMember => {
655
767
  if (priorCompleted.has(m.component)) return { ...m, status: "applied" };
656
768
  const later = roundOf.get(m.component);
657
769
  const fresh = later?.set.members.find((x) => x.member === m.member);
658
- const asPlanned: PrMember = later && fresh ? { ...m, planDigest: fresh.planDigest, counts: fresh.counts, round: later.round } : m;
770
+ const planned: PrMember = later && fresh ? { ...m, planDigest: fresh.planDigest, counts: fresh.counts, round: later.round } : m;
771
+ // #4446: how its part of a group's apply ended, as the capability said.
772
+ const groupOutcome = run.groupOutcomes?.[m.component];
773
+ const asPlanned: PrMember = groupOutcome ? { ...planned, groupOutcome } : planned;
659
774
  if (completed.has(m.component)) return { ...asPlanned, status: "applied" };
775
+ if (notStarted.has(m.component)) {
776
+ const error = errorOf(m.component);
777
+ return { ...asPlanned, status: "not-started", ...(error ? { error } : {}) };
778
+ }
660
779
  const h = held.get(m.component);
661
780
  if (h) return { ...asPlanned, status: "waiting", held: h.heldBy ? `${h.heldBy} is held: ${h.reason}` : h.reason };
662
781
  if (fresh?.status === "plan-failed") return { ...asPlanned, status: "plan-failed", ...(fresh.error ? { error: fresh.error } : {}) };
@@ -763,6 +882,12 @@ export interface PrResumeOptions {
763
882
  record: PrApplyRecord;
764
883
  /** Components an earlier attempt applied. */
765
884
  completed: string[];
885
+ /**
886
+ * The outputs the components in `completed` left (#4446). A member left
887
+ * of an approved group plans against the outputs its group-mates applied
888
+ * with, which the group's apply computed and the approved plan did not know.
889
+ */
890
+ applied?: Record<string, Record<string, unknown>>;
766
891
  }
767
892
 
768
893
  export type PrResume =
@@ -796,20 +921,44 @@ export type PrResume =
796
921
  * is `record.digest`, and the document gives that digest. The digest binds
797
922
  * the entries, holes and side effects {@link planCoveredBy} reads (#3555),
798
923
  * so a record cannot widen what a re-planned member may do.
924
+ *
925
+ * A group that failed part way (ws-107, "Resume"; #4446) leaves its
926
+ * `partial`, `failed` and `not-started` members; `applied` and `no-changes`
927
+ * ones are in `completed`. What is left of each approved group is grouped
928
+ * again ({@link regroupRemaining}), so it plans as a smaller group, or one
929
+ * by one when the rule or the capability says so. The smaller group's
930
+ * digest differs from the approved one, so coverage rests on entries and
931
+ * shapes, as for any resumed member.
799
932
  */
800
933
  export async function resumePrSet(options: PrResumeOptions): Promise<PrResume> {
801
934
  const { record } = options;
802
935
  if (record.changeSet.digest !== record.digest || !verifyChangeSetDigest(record.changeSet)) {
803
936
  return { ok: false, reasons: [`the record's change set is not the one its digest ${record.digest} names`] };
804
937
  }
805
- const plan = remainingFanOut(options.plan, options.components, { completed: options.completed });
938
+ const plan = await regroupRemaining({
939
+ components: options.components,
940
+ plan: remainingFanOut(options.plan, options.components, { completed: options.completed }),
941
+ registry: options.registry,
942
+ env: options.env,
943
+ ...(options.vars ? { vars: options.vars } : {}),
944
+ groups: options.plan.groups,
945
+ ...(options.plan.fallbacks ? { fallbacks: options.plan.fallbacks } : {}),
946
+ });
947
+ // The outputs of the applied members of a group with members left: the values the group's apply computed.
948
+ const left = new Set(plan.order);
949
+ const fromGroup = Object.fromEntries(
950
+ (options.plan.groups ?? [])
951
+ .filter((g) => g.components.some((c) => left.has(c)))
952
+ .flatMap((g) => g.components.filter((c) => !left.has(c) && options.applied?.[c] !== undefined))
953
+ .map((c) => [c, options.applied![c]!]),
954
+ );
806
955
  const fresh = await planPrSet({
807
956
  components: options.components,
808
957
  plan,
809
958
  registry: options.registry,
810
959
  env: options.env,
811
960
  ...(options.vars ? { vars: options.vars } : {}),
812
- seededOutputs: { ...(options.seededOutputs ?? {}), ...options.record.planOutputs },
961
+ seededOutputs: { ...(options.seededOutputs ?? {}), ...options.record.planOutputs, ...fromGroup },
813
962
  ...(options.targets ? { targets: options.targets } : {}),
814
963
  });
815
964
  // #4422: decided as a round decides (`coveredRound`), by the same function a wave uses.
@@ -823,7 +972,9 @@ export async function resumePrSet(options: PrResumeOptions): Promise<PrResume> {
823
972
  doc: options.record.changeSet,
824
973
  members: options.record.members.map((m) => ({ ...m, status: "planned" as const })),
825
974
  plans: fresh.plans,
826
- outputs: options.record.planOutputs,
975
+ outputs: { ...options.record.planOutputs, ...fromGroup },
976
+ ...(fresh.groups ? { groups: fresh.groups } : {}),
977
+ ...(fresh.fallbacks ? { fallbacks: fresh.fallbacks } : {}),
827
978
  failed: false,
828
979
  },
829
980
  };
@@ -843,7 +994,17 @@ export function prWavePlan(plan: FanOutPlan, components: DriverComponent[], wave
843
994
  const seeds = [
844
995
  ...new Set([...plan.seeds, ...order.flatMap((name) => (byName.get(name)?.dependsOn ?? []).filter((dep) => !inWave.has(dep)))]),
845
996
  ].sort();
846
- return { order, waves: order.length > 0 ? [order] : [], skipped: [], seeds, indeterminate: plan.indeterminate, digest: plan.digest };
997
+ // ws-107: a group sits in one wave, so what is left of it plans and applies as one there.
998
+ const groups = (plan.groups ?? []).map((g) => ({ ...g, components: g.components.filter((c) => inWave.has(c)) })).filter((g) => g.components.length > 1);
999
+ return {
1000
+ order,
1001
+ waves: order.length > 0 ? [order] : [],
1002
+ skipped: [],
1003
+ seeds,
1004
+ indeterminate: plan.indeterminate,
1005
+ digest: plan.digest,
1006
+ ...(groups.length > 0 ? { groups } : {}),
1007
+ };
847
1008
  }
848
1009
 
849
1010
  /**
@@ -928,9 +1089,36 @@ export interface PrReportInput {
928
1089
  migrations?: PrMigrationFile[];
929
1090
  }
930
1091
 
1092
+ /**
1093
+ * The report's `groups` (#4446): each group the set planned, with the wave
1094
+ * of `waves` its members sit in and the reads of group-mates its members'
1095
+ * change-set parts name, which the group's apply computes.
1096
+ */
1097
+ function reportGroups(set: PrPlanSet, waves: readonly string[][]): PrGroup[] {
1098
+ const componentOf = new Map(set.members.map((m) => [m.member, m.component]));
1099
+ return (set.groups ?? []).map((g) => {
1100
+ const wave = waves.findIndex((w) => g.components.some((c) => w.includes(c))) + 1;
1101
+ const reads = set.doc.members
1102
+ .filter((m) => g.components.includes(componentOf.get(m.member) ?? m.member))
1103
+ .flatMap((m) => (m.group?.reads ?? []).map((ref) => ({ component: componentOf.get(m.member) ?? m.member, ref })))
1104
+ .sort((a, b) => a.component.localeCompare(b.component) || a.ref.localeCompare(b.ref));
1105
+ return {
1106
+ capability: g.capability,
1107
+ components: [...g.components],
1108
+ wave: wave > 0 ? wave : 1,
1109
+ digest: g.digest,
1110
+ ...(g.error !== undefined ? { error: g.error } : {}),
1111
+ ...(reads.length > 0 ? { reads } : {}),
1112
+ };
1113
+ });
1114
+ }
1115
+
931
1116
  /** The report a stage writes. */
932
1117
  export function prReport(input: PrReportInput): PrReport {
933
1118
  const changed = new Set(input.signal.changed);
1119
+ const groups = reportGroups(input.set, input.plan.waves);
1120
+ const groupOf = new Map(groups.flatMap((g, i) => g.components.map((c) => [c, i] as const)));
1121
+ const members = (input.members ?? input.set.members).map((m) => (groupOf.has(m.component) ? { ...m, group: groupOf.get(m.component)! } : m));
934
1122
  return {
935
1123
  $schema: PR_REPORT_SCHEMA_ID,
936
1124
  contract: PR_REPORT_CONTRACT,
@@ -961,7 +1149,9 @@ export function prReport(input: PrReportInput): PrReport {
961
1149
  indeterminate: input.plan.indeterminate,
962
1150
  waves: input.plan.waves,
963
1151
  },
964
- members: input.members ?? input.set.members,
1152
+ ...(groups.length > 0 ? { groups } : {}),
1153
+ ...(input.set.fallbacks?.length ? { fallbacks: input.set.fallbacks.map((f) => ({ ...f })) } : {}),
1154
+ members,
965
1155
  changeSet: input.set.doc,
966
1156
  };
967
1157
  }
@@ -22,6 +22,12 @@
22
22
  * A stage whose components declare no gate has nowhere to wait, so the
23
23
  * promote stops there with nothing more deployed.
24
24
  *
25
+ * - **Groups** (ws-107, #4465). With `grouped`, the waves are the grouped
26
+ * fan-out's, so each group sits in one wave and is checked, planned again
27
+ * and run as one: when any member's input moved, every member left of the
28
+ * group is planned again with it. Under `--wave-gate` no group forms
29
+ * (`formRunGroups`), so there is none here.
30
+ *
25
31
  * A promote that stops at a round's or a wave's gate writes a resume record
26
32
  * ({@link PromoteResume}): the components that applied and the outputs they
27
33
  * left. The same promote run again with it skips what applied and carries on
@@ -37,6 +43,8 @@ import { waveGateName } from "../gated-waves";
37
43
  import { capabilitySteps } from "./wave-plan";
38
44
  import type { ChangeSetDocument } from "../change-set";
39
45
  import type { ReleasePlan } from "./release-gate";
46
+ import type { FanOutGroup, FanOutPlan } from "./fan-out";
47
+ import { groupsByComponent } from "./groups";
40
48
 
41
49
  export const PROMOTE_RESUME_SCHEMA = "chant-promote-resume/v1";
42
50
 
@@ -90,6 +98,8 @@ export interface PromoteRoundsOptions {
90
98
  replan: (only: ReadonlySet<string>, outputs: Record<string, Record<string, unknown>>) => Promise<ChangeSetDocument>;
91
99
  /** Store a stage's plan and bind its gates (empty resolutions included). */
92
100
  bind: (doc: ChangeSetDocument, gates: string[]) => Promise<ReleaseGateBinding>;
101
+ /** The grouped fan-out (#4465): its waves replace the ones `dependsOn` gives, and each group is checked as one. */
102
+ grouped?: FanOutPlan;
93
103
  }
94
104
 
95
105
  export interface PromoteRoundsResult {
@@ -169,7 +179,17 @@ function joinRuns(runs: DriverRunResult[], outputs: Record<string, Record<string
169
179
  */
170
180
  export async function runPromotionInRounds(options: PromoteRoundsOptions): Promise<PromoteRoundsResult> {
171
181
  const byName = new Map(options.components.map((c) => [c.name, c]));
172
- const { waves } = resolveComponentGraph(options.components.map((c) => ({ ...c, dependsOn: (c.dependsOn ?? []).filter((d) => byName.has(d)) })));
182
+ const grouped = options.grouped?.groups?.length ? options.grouped : undefined;
183
+ const placed = new Set(grouped?.order ?? []);
184
+ // A grouped fan-out over what a resume left holds no wave for what applied earlier; that is skipped anyway.
185
+ const waves = grouped
186
+ ? grouped.waves.map((w) => w.filter((name) => byName.has(name)))
187
+ : resolveComponentGraph(options.components.map((c) => ({ ...c, dependsOn: (c.dependsOn ?? []).filter((d) => byName.has(d)) }))).waves;
188
+ if (grouped) {
189
+ const missing = options.components.filter((c) => !placed.has(c.name) && !(options.completed ?? []).includes(c.name)).map((c) => c.name);
190
+ if (missing.length > 0) throw new Error(`the grouped waves leave out ${missing.join(", ")}, which this promote runs`);
191
+ }
192
+ const groupOf = grouped ? groupsByComponent(grouped) : new Map<string, FanOutGroup>();
173
193
  const completed = [...(options.completed ?? [])];
174
194
  const done = new Set(completed);
175
195
  let outputs = { ...options.outputs };
@@ -181,9 +201,12 @@ export async function runPromotionInRounds(options: PromoteRoundsOptions): Promi
181
201
  const waveNumber = index + 1;
182
202
  const pending = wave.filter((name) => !done.has(name));
183
203
  if (pending.length === 0) continue;
184
- const check = options.waveGate !== undefined && waveNumber > 1
204
+ const moved = options.waveGate !== undefined && waveNumber > 1
185
205
  ? pending
186
206
  : pending.filter((name) => movedInputs(byName.get(name)!, options.reads[name], outputs).length > 0);
207
+ // A group is planned again as one: a member whose input moved takes the rest of its group with it.
208
+ const withMates = new Set(moved.flatMap((name) => [name, ...(groupOf.get(name)?.components ?? [])]));
209
+ const check = pending.filter((name) => withMates.has(name));
187
210
  const batches: Array<{ names: string[]; binding: ReleaseGateBinding; stage?: PromoteStage; rename?: (g: string) => string }> = [];
188
211
  const rest = pending.filter((name) => !check.includes(name));
189
212
  if (check.length > 0) {