@intentius/chant 0.124.51 → 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 (593) 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 +17 -3
  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 +224 -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/covered-round.d.ts +173 -0
  120. package/dist/components/covered-round.d.ts.map +1 -0
  121. package/dist/components/covered-round.js +200 -0
  122. package/dist/components/declared-reads.d.ts +38 -0
  123. package/dist/components/declared-reads.d.ts.map +1 -0
  124. package/dist/components/declared-reads.js +131 -0
  125. package/dist/components/deploy-units.d.ts.map +1 -1
  126. package/dist/components/deploy-units.js +4 -0
  127. package/dist/components/driver.d.ts +42 -1
  128. package/dist/components/driver.d.ts.map +1 -1
  129. package/dist/components/driver.js +74 -3
  130. package/dist/components/fan-out-run.d.ts +23 -1
  131. package/dist/components/fan-out-run.d.ts.map +1 -1
  132. package/dist/components/fan-out-run.js +82 -14
  133. package/dist/components/fan-out-support.d.ts +6 -0
  134. package/dist/components/fan-out-support.d.ts.map +1 -1
  135. package/dist/components/fan-out-support.js +4 -1
  136. package/dist/components/fan-out.d.ts +28 -0
  137. package/dist/components/fan-out.d.ts.map +1 -1
  138. package/dist/components/groups.d.ts +229 -0
  139. package/dist/components/groups.d.ts.map +1 -0
  140. package/dist/components/groups.js +760 -0
  141. package/dist/components/index.d.ts +7 -2
  142. package/dist/components/index.d.ts.map +1 -1
  143. package/dist/components/index.js +5 -0
  144. package/dist/components/plan-shape-conformance.d.ts +48 -0
  145. package/dist/components/plan-shape-conformance.d.ts.map +1 -0
  146. package/dist/components/plan-shape-conformance.js +122 -0
  147. package/dist/components/pr-run.d.ts +62 -7
  148. package/dist/components/pr-run.d.ts.map +1 -1
  149. package/dist/components/pr-run.js +354 -53
  150. package/dist/components/promote-rounds.d.ts +9 -0
  151. package/dist/components/promote-rounds.d.ts.map +1 -1
  152. package/dist/components/promote-rounds.js +23 -2
  153. package/dist/components/promote.d.ts +17 -0
  154. package/dist/components/promote.d.ts.map +1 -1
  155. package/dist/components/promote.js +36 -6
  156. package/dist/components/release-gate.d.ts +23 -1
  157. package/dist/components/release-gate.d.ts.map +1 -1
  158. package/dist/components/release-gate.js +97 -33
  159. package/dist/components/run-groups.d.ts +58 -0
  160. package/dist/components/run-groups.d.ts.map +1 -0
  161. package/dist/components/run-groups.js +78 -0
  162. package/dist/components/starter-plugin.d.ts +1 -0
  163. package/dist/components/starter-plugin.d.ts.map +1 -1
  164. package/dist/components/starter-plugin.js +6 -1
  165. package/dist/components/verbs/index.d.ts +1 -0
  166. package/dist/components/verbs/index.d.ts.map +1 -1
  167. package/dist/components/verbs/index.js +1 -0
  168. package/dist/components/verbs/member-outputs.d.ts +187 -0
  169. package/dist/components/verbs/member-outputs.d.ts.map +1 -0
  170. package/dist/components/verbs/member-outputs.js +218 -0
  171. package/dist/components/wave-plan.d.ts +2 -0
  172. package/dist/components/wave-plan.d.ts.map +1 -1
  173. package/dist/components/wave-plan.js +4 -1
  174. package/dist/config.d.ts +60 -0
  175. package/dist/config.d.ts.map +1 -1
  176. package/dist/config.js +27 -0
  177. package/dist/deep-observation.d.ts +1 -13
  178. package/dist/deep-observation.d.ts.map +1 -1
  179. package/dist/deep-observation.js +5 -22
  180. package/dist/drift-record.d.ts +542 -0
  181. package/dist/drift-record.d.ts.map +1 -0
  182. package/dist/drift-record.js +886 -0
  183. package/dist/engine-facts.d.ts +144 -0
  184. package/dist/engine-facts.d.ts.map +1 -0
  185. package/dist/engine-facts.js +14 -0
  186. package/dist/graph-ir.d.ts +153 -2
  187. package/dist/graph-ir.d.ts.map +1 -1
  188. package/dist/graph-ir.js +224 -6
  189. package/dist/graph-refs.d.ts +145 -0
  190. package/dist/graph-refs.d.ts.map +1 -1
  191. package/dist/graph-refs.js +305 -0
  192. package/dist/graph-schema/fixtures/adopt.json +15 -0
  193. package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  194. package/dist/graph-schema/fixtures/index.json +68 -0
  195. package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  196. package/dist/graph-schema/fixtures.d.ts +23 -0
  197. package/dist/graph-schema/fixtures.d.ts.map +1 -0
  198. package/dist/graph-schema/fixtures.js +246 -0
  199. package/dist/graph-schema/generate.d.ts +15 -0
  200. package/dist/graph-schema/generate.d.ts.map +1 -0
  201. package/dist/graph-schema/generate.js +267 -0
  202. package/dist/graph-schema/graph.schema.json +1387 -0
  203. package/dist/health.schema.json +2 -1
  204. package/dist/identity.d.ts.map +1 -1
  205. package/dist/identity.js +2 -0
  206. package/dist/index.d.ts +1 -0
  207. package/dist/index.d.ts.map +1 -1
  208. package/dist/index.js +2 -0
  209. package/dist/instance-nodes.d.ts +159 -0
  210. package/dist/instance-nodes.d.ts.map +1 -0
  211. package/dist/instance-nodes.js +231 -0
  212. package/dist/lexicon.d.ts +72 -0
  213. package/dist/lexicon.d.ts.map +1 -1
  214. package/dist/lifecycle/adoption-file.d.ts +56 -0
  215. package/dist/lifecycle/adoption-file.d.ts.map +1 -0
  216. package/dist/lifecycle/adoption-file.js +123 -0
  217. package/dist/lifecycle/cloud-index.d.ts +186 -0
  218. package/dist/lifecycle/cloud-index.d.ts.map +1 -0
  219. package/dist/lifecycle/cloud-index.js +618 -0
  220. package/dist/lifecycle/derive-edges.d.ts +59 -0
  221. package/dist/lifecycle/derive-edges.d.ts.map +1 -0
  222. package/dist/lifecycle/derive-edges.js +126 -0
  223. package/dist/lifecycle/git.d.ts +10 -9
  224. package/dist/lifecycle/git.d.ts.map +1 -1
  225. package/dist/lifecycle/git.js +7 -1
  226. package/dist/lifecycle/identity-match.d.ts +133 -0
  227. package/dist/lifecycle/identity-match.d.ts.map +1 -0
  228. package/dist/lifecycle/identity-match.js +147 -0
  229. package/dist/lifecycle/index-diff.d.ts +224 -0
  230. package/dist/lifecycle/index-diff.d.ts.map +1 -0
  231. package/dist/lifecycle/index-diff.js +327 -0
  232. package/dist/lifecycle/index-document.d.ts +130 -0
  233. package/dist/lifecycle/index-document.d.ts.map +1 -0
  234. package/dist/lifecycle/index-document.js +189 -0
  235. package/dist/lifecycle/index-drift.d.ts +121 -0
  236. package/dist/lifecycle/index-drift.d.ts.map +1 -0
  237. package/dist/lifecycle/index-drift.js +178 -0
  238. package/dist/lifecycle/index-import.d.ts +168 -0
  239. package/dist/lifecycle/index-import.d.ts.map +1 -0
  240. package/dist/lifecycle/index-import.js +515 -0
  241. package/dist/lifecycle/index-join.d.ts +129 -0
  242. package/dist/lifecycle/index-join.d.ts.map +1 -0
  243. package/dist/lifecycle/index-join.js +248 -0
  244. package/dist/lifecycle/index.d.ts +4 -0
  245. package/dist/lifecycle/index.d.ts.map +1 -1
  246. package/dist/lifecycle/index.js +4 -0
  247. package/dist/lifecycle/observation-baseline.d.ts +61 -21
  248. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  249. package/dist/lifecycle/observation-baseline.js +144 -19
  250. package/dist/lifecycle/observe.d.ts.map +1 -1
  251. package/dist/lifecycle/observe.js +6 -1
  252. package/dist/lifecycle/query.d.ts +45 -4
  253. package/dist/lifecycle/query.d.ts.map +1 -1
  254. package/dist/lifecycle/query.js +89 -7
  255. package/dist/lifecycle/replay.d.ts +10 -3
  256. package/dist/lifecycle/replay.d.ts.map +1 -1
  257. package/dist/lifecycle/replay.js +10 -6
  258. package/dist/lifecycle/scenario.d.ts +4 -3
  259. package/dist/lifecycle/scenario.d.ts.map +1 -1
  260. package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
  261. package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
  262. package/dist/lifecycle/snapshot-store-fake.js +21 -0
  263. package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
  264. package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
  265. package/dist/lifecycle/snapshot-store-s3.js +254 -0
  266. package/dist/lifecycle/snapshot-store.d.ts +170 -0
  267. package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
  268. package/dist/lifecycle/snapshot-store.js +832 -0
  269. package/dist/lifecycle/snapshot.d.ts +11 -0
  270. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  271. package/dist/lifecycle/snapshot.js +18 -5
  272. package/dist/lifecycle/type-listing.d.ts +148 -0
  273. package/dist/lifecycle/type-listing.d.ts.map +1 -0
  274. package/dist/lifecycle/type-listing.js +166 -0
  275. package/dist/lint/component-checks.d.ts +15 -1
  276. package/dist/lint/component-checks.d.ts.map +1 -1
  277. package/dist/lint/component-checks.js +1 -0
  278. package/dist/lint/presets/relaxed.json +2 -1
  279. package/dist/lint/presets/strict.json +2 -1
  280. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
  281. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
  282. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
  283. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
  284. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
  285. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
  286. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
  287. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
  288. package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
  289. package/dist/lint/rules/comp/index.d.ts +2 -1
  290. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  291. package/dist/lint/rules/comp/index.js +4 -1
  292. package/dist/observation.d.ts +19 -2
  293. package/dist/observation.d.ts.map +1 -1
  294. package/dist/observation.js +18 -1
  295. package/dist/op/activities/activity-contracts.d.ts +1 -1
  296. package/dist/op/activities/activity-contracts.js +1 -1
  297. package/dist/op/activities/change-set.d.ts +6 -4
  298. package/dist/op/activities/change-set.d.ts.map +1 -1
  299. package/dist/op/activities/change-set.js +4 -2
  300. package/dist/op/activity-failure.d.ts +27 -0
  301. package/dist/op/activity-failure.d.ts.map +1 -1
  302. package/dist/op/activity-failure.js +31 -0
  303. package/dist/op/index.d.ts +2 -1
  304. package/dist/op/index.d.ts.map +1 -1
  305. package/dist/op/index.js +1 -1
  306. package/dist/op/local-executor.d.ts +5 -1
  307. package/dist/op/local-executor.d.ts.map +1 -1
  308. package/dist/op/local-executor.js +22 -8
  309. package/dist/pr-loop.d.ts +92 -3
  310. package/dist/pr-loop.d.ts.map +1 -1
  311. package/dist/pr-loop.js +62 -3
  312. package/dist/pulumi-urn.d.ts +101 -0
  313. package/dist/pulumi-urn.d.ts.map +1 -0
  314. package/dist/pulumi-urn.js +131 -0
  315. package/dist/release-floors.d.ts +23 -0
  316. package/dist/release-floors.d.ts.map +1 -0
  317. package/dist/release-floors.js +22 -0
  318. package/dist/sensitive-key.d.ts +23 -0
  319. package/dist/sensitive-key.d.ts.map +1 -0
  320. package/dist/sensitive-key.js +31 -0
  321. package/dist/wave-runtime.mjs +234 -234
  322. package/dist/workspace/adoption-report.d.ts +53 -0
  323. package/dist/workspace/adoption-report.d.ts.map +1 -0
  324. package/dist/workspace/adoption-report.js +76 -0
  325. package/dist/workspace/capabilities-contract.d.ts +7 -0
  326. package/dist/workspace/capabilities-contract.d.ts.map +1 -1
  327. package/dist/workspace/capabilities-contract.js +2 -1
  328. package/dist/workspace/capabilities.schema.json +98 -0
  329. package/dist/workspace/change-set.schema.json +42 -3
  330. package/dist/workspace/ci-cloud-identity.d.ts +13 -1
  331. package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
  332. package/dist/workspace/ci-cloud-identity.js +30 -1
  333. package/dist/workspace/compose-graph.d.ts +10 -1
  334. package/dist/workspace/compose-graph.d.ts.map +1 -1
  335. package/dist/workspace/compose-graph.js +6 -1
  336. package/dist/workspace/conformance/index.d.ts +1 -1
  337. package/dist/workspace/conformance/index.d.ts.map +1 -1
  338. package/dist/workspace/conformance/index.js +6 -1
  339. package/dist/workspace/drift-cli.d.ts +81 -0
  340. package/dist/workspace/drift-cli.d.ts.map +1 -0
  341. package/dist/workspace/drift-cli.js +317 -0
  342. package/dist/workspace/drift.schema.json +672 -0
  343. package/dist/workspace/graph-cli.d.ts +2 -0
  344. package/dist/workspace/graph-cli.d.ts.map +1 -1
  345. package/dist/workspace/graph-cli.js +3 -0
  346. package/dist/workspace/graph.schema.json +72 -0
  347. package/dist/workspace/init.d.ts +5 -1
  348. package/dist/workspace/init.d.ts.map +1 -1
  349. package/dist/workspace/init.js +55 -9
  350. package/dist/workspace/ledger.schema.json +1 -1
  351. package/dist/workspace/nested-graph.d.ts.map +1 -1
  352. package/dist/workspace/nested-graph.js +2 -0
  353. package/dist/workspace/pr-report.schema.json +76 -3
  354. package/dist/workspace/promote-waves.d.ts +112 -0
  355. package/dist/workspace/promote-waves.d.ts.map +1 -0
  356. package/dist/workspace/promote-waves.js +290 -0
  357. package/dist/workspace/promote.d.ts +84 -2
  358. package/dist/workspace/promote.d.ts.map +1 -1
  359. package/dist/workspace/promote.js +347 -61
  360. package/dist/workspace/reason-codes.d.ts +12 -0
  361. package/dist/workspace/reason-codes.d.ts.map +1 -1
  362. package/dist/workspace/reason-codes.js +14 -0
  363. package/dist/workspace/search-contract.d.ts +3 -3
  364. package/dist/workspace/search-contract.d.ts.map +1 -1
  365. package/dist/workspace/search-contract.js +2 -0
  366. package/dist/workspace/search.schema.json +103 -5
  367. package/package.json +14 -1
  368. package/src/__fixtures__/drift-record/README.md +58 -0
  369. package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
  370. package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
  371. package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
  372. package/src/__fixtures__/drift-record/component-status.json +91 -0
  373. package/src/__fixtures__/drift-record/deep-diff.json +259 -0
  374. package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
  375. package/src/__fixtures__/drift-record/live-diff.json +125 -0
  376. package/src/audit/discover.e2e.test.ts +14 -0
  377. package/src/audit/discover.ts +3 -1
  378. package/src/behaviour.ts +1 -1
  379. package/src/capabilities/templates.json +145 -0
  380. package/src/capabilities/templates.test.ts +212 -0
  381. package/src/capabilities/templates.ts +204 -0
  382. package/src/change-set-bundle.test.ts +83 -0
  383. package/src/change-set-graph.ts +80 -9
  384. package/src/change-set.test.ts +24 -0
  385. package/src/change-set.ts +77 -14
  386. package/src/ci-identity-targets.test.ts +23 -0
  387. package/src/ci-identity.ts +10 -4
  388. package/src/cli/commands/import-index.test.ts +117 -0
  389. package/src/cli/commands/import-index.ts +265 -0
  390. package/src/cli/commands/lint.ts +4 -2
  391. package/src/cli/handlers/components.test.ts +9 -0
  392. package/src/cli/handlers/components.ts +12 -2
  393. package/src/cli/handlers/fan-out.ts +17 -3
  394. package/src/cli/handlers/graph.test.ts +42 -0
  395. package/src/cli/handlers/graph.ts +26 -4
  396. package/src/cli/handlers/index-cloud.ts +32 -0
  397. package/src/cli/handlers/index-diff.test.ts +68 -0
  398. package/src/cli/handlers/index-diff.ts +146 -0
  399. package/src/cli/handlers/ledger-root-member.test.ts +161 -0
  400. package/src/cli/handlers/lifecycle-drift.ts +68 -0
  401. package/src/cli/handlers/lifecycle.test.ts +33 -11
  402. package/src/cli/handlers/lifecycle.ts +205 -30
  403. package/src/cli/handlers/misc.ts +73 -0
  404. package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
  405. package/src/cli/handlers/pr.ts +16 -3
  406. package/src/cli/handlers/promote.test.ts +64 -0
  407. package/src/cli/handlers/promote.ts +81 -6
  408. package/src/cli/handlers/query.ts +27 -4
  409. package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
  410. package/src/cli/handlers/run-groups-cli.test.ts +229 -0
  411. package/src/cli/handlers/scenario.test.ts +13 -0
  412. package/src/cli/handlers/scenario.ts +8 -7
  413. package/src/cli/handlers/search-index.test.ts +255 -0
  414. package/src/cli/handlers/search-json.test.ts +1 -1
  415. package/src/cli/handlers/search-ownership.test.ts +194 -0
  416. package/src/cli/handlers/search.ts +271 -38
  417. package/src/cli/main.test.ts +7 -0
  418. package/src/cli/main.ts +56 -1
  419. package/src/cli/mcp/lifecycle-tools.ts +3 -2
  420. package/src/cli/mcp/resource-handlers.ts +4 -3
  421. package/src/cli/mcp/workspace-tools.ts +1 -1
  422. package/src/cli/parse-args.ts +40 -2
  423. package/src/cli/registry.ts +19 -1
  424. package/src/cloud-lister/aws.test.ts +202 -0
  425. package/src/cloud-lister/aws.ts +226 -0
  426. package/src/cloud-lister/azure.test.ts +132 -0
  427. package/src/cloud-lister/azure.ts +142 -0
  428. package/src/cloud-lister/common.ts +115 -0
  429. package/src/cloud-lister/credentials.ts +110 -0
  430. package/src/cloud-lister/fakes.ts +66 -0
  431. package/src/cloud-lister/gcp.test.ts +121 -0
  432. package/src/cloud-lister/gcp.ts +159 -0
  433. package/src/cloud-lister/guard.test.ts +198 -0
  434. package/src/cloud-lister/guard.ts +111 -0
  435. package/src/cloud-lister/ids.ts +54 -0
  436. package/src/cloud-lister/index.ts +134 -0
  437. package/src/cloud-lister/kubernetes.test.ts +160 -0
  438. package/src/cloud-lister/kubernetes.ts +284 -0
  439. package/src/cloud-lister/markers.ts +74 -0
  440. package/src/cloud-lister/mask.ts +77 -0
  441. package/src/cloud-lister/shape.test.ts +81 -0
  442. package/src/cloud-lister/sigv4.ts +70 -0
  443. package/src/cloud-lister/transport.ts +118 -0
  444. package/src/cloud-lister/types.ts +135 -0
  445. package/src/components/capability-plugin-loader.ts +4 -0
  446. package/src/components/capability-plugin.ts +10 -0
  447. package/src/components/capability.ts +238 -0
  448. package/src/components/cli-support.ts +70 -14
  449. package/src/components/covered-round.test.ts +166 -0
  450. package/src/components/covered-round.ts +293 -0
  451. package/src/components/declared-reads.test.ts +103 -0
  452. package/src/components/declared-reads.ts +134 -0
  453. package/src/components/deploy-units.ts +4 -0
  454. package/src/components/driver.ts +101 -4
  455. package/src/components/fan-out-run.ts +95 -15
  456. package/src/components/fan-out-support.ts +10 -1
  457. package/src/components/fan-out.ts +30 -0
  458. package/src/components/groups.test.ts +320 -0
  459. package/src/components/groups.ts +880 -0
  460. package/src/components/index.ts +59 -0
  461. package/src/components/plan-shape-conformance.ts +130 -0
  462. package/src/components/pr-env-run-bundle.test.ts +2 -0
  463. package/src/components/pr-run.test.ts +728 -4
  464. package/src/components/pr-run.ts +395 -55
  465. package/src/components/promote-rounds.ts +25 -2
  466. package/src/components/promote.ts +55 -5
  467. package/src/components/registry.test.ts +1 -1
  468. package/src/components/release-gate.ts +115 -30
  469. package/src/components/release-groups.test.ts +362 -0
  470. package/src/components/run-groups.ts +113 -0
  471. package/src/components/starter-plugin.ts +6 -0
  472. package/src/components/verbs/index.ts +1 -0
  473. package/src/components/verbs/member-outputs.test.ts +205 -0
  474. package/src/components/verbs/member-outputs.ts +308 -0
  475. package/src/components/wave-plan.ts +5 -2
  476. package/src/config.ts +44 -0
  477. package/src/deep-observation.ts +5 -23
  478. package/src/drift-record.test.ts +596 -0
  479. package/src/drift-record.ts +1206 -0
  480. package/src/engine-facts.guard.test.ts +111 -0
  481. package/src/engine-facts.test.ts +178 -0
  482. package/src/engine-facts.ts +154 -0
  483. package/src/gated-waves-bundle.test.ts +2 -0
  484. package/src/graph-ir-live.test.ts +92 -0
  485. package/src/graph-ir.ts +317 -7
  486. package/src/graph-refs.ts +416 -0
  487. package/src/graph-schema/fixtures/adopt.json +15 -0
  488. package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  489. package/src/graph-schema/fixtures/index.json +68 -0
  490. package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  491. package/src/graph-schema/fixtures.ts +267 -0
  492. package/src/graph-schema/generate.ts +263 -0
  493. package/src/graph-schema/graph-schema.test.ts +153 -0
  494. package/src/graph-schema/graph.schema.json +1387 -0
  495. package/src/health.schema.json +2 -1
  496. package/src/identity.ts +2 -0
  497. package/src/index.ts +2 -0
  498. package/src/instance-nodes.ts +288 -0
  499. package/src/lexicon.ts +76 -0
  500. package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
  501. package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
  502. package/src/lifecycle/adoption-file.test.ts +100 -0
  503. package/src/lifecycle/adoption-file.ts +154 -0
  504. package/src/lifecycle/cloud-index.test.ts +517 -0
  505. package/src/lifecycle/cloud-index.ts +736 -0
  506. package/src/lifecycle/derive-edges.test.ts +193 -0
  507. package/src/lifecycle/derive-edges.ts +182 -0
  508. package/src/lifecycle/git.ts +10 -4
  509. package/src/lifecycle/identity-match.test.ts +308 -0
  510. package/src/lifecycle/identity-match.ts +257 -0
  511. package/src/lifecycle/index-diff.test.ts +198 -0
  512. package/src/lifecycle/index-diff.ts +476 -0
  513. package/src/lifecycle/index-document.test.ts +64 -0
  514. package/src/lifecycle/index-document.ts +299 -0
  515. package/src/lifecycle/index-drift.test.ts +191 -0
  516. package/src/lifecycle/index-drift.ts +304 -0
  517. package/src/lifecycle/index-import.test.ts +255 -0
  518. package/src/lifecycle/index-import.ts +634 -0
  519. package/src/lifecycle/index-join.test.ts +250 -0
  520. package/src/lifecycle/index-join.ts +304 -0
  521. package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
  522. package/src/lifecycle/index.ts +4 -0
  523. package/src/lifecycle/member-ledger.test.ts +25 -7
  524. package/src/lifecycle/observation-baseline.test.ts +72 -1
  525. package/src/lifecycle/observation-baseline.ts +190 -27
  526. package/src/lifecycle/observe.ts +6 -1
  527. package/src/lifecycle/query.test.ts +69 -1
  528. package/src/lifecycle/query.ts +103 -10
  529. package/src/lifecycle/replay.test.ts +9 -5
  530. package/src/lifecycle/replay.ts +9 -5
  531. package/src/lifecycle/scenario.ts +4 -3
  532. package/src/lifecycle/snapshot-store-fake.ts +29 -0
  533. package/src/lifecycle/snapshot-store-s3.ts +316 -0
  534. package/src/lifecycle/snapshot-store.test.ts +385 -0
  535. package/src/lifecycle/snapshot-store.ts +981 -0
  536. package/src/lifecycle/snapshot.test.ts +13 -0
  537. package/src/lifecycle/snapshot.ts +23 -6
  538. package/src/lifecycle/type-listing.test.ts +166 -0
  539. package/src/lifecycle/type-listing.ts +309 -0
  540. package/src/lint/component-checks.ts +17 -1
  541. package/src/lint/config.test.ts +3 -0
  542. package/src/lint/presets/relaxed.json +2 -1
  543. package/src/lint/presets/strict.json +2 -1
  544. package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
  545. package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
  546. package/src/lint/rules/comp/comp.test.ts +2 -1
  547. package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
  548. package/src/lint/rules/comp/comp008.test.ts +77 -0
  549. package/src/lint/rules/comp/index.ts +4 -1
  550. package/src/observation-edges.test.ts +33 -0
  551. package/src/observation.ts +36 -1
  552. package/src/op/activities/activity-contracts.ts +1 -1
  553. package/src/op/activities/change-set.ts +8 -5
  554. package/src/op/activity-failure.ts +36 -0
  555. package/src/op/index.ts +2 -1
  556. package/src/op/local-executor.test.ts +80 -0
  557. package/src/op/local-executor.ts +27 -9
  558. package/src/pr-loop-bundle.test.ts +5 -2
  559. package/src/pr-loop.test.ts +57 -1
  560. package/src/pr-loop.ts +154 -6
  561. package/src/pulumi-urn.ts +151 -0
  562. package/src/release-floors.ts +22 -0
  563. package/src/sensitive-key.ts +34 -0
  564. package/src/workspace/adoption-report.test.ts +61 -0
  565. package/src/workspace/adoption-report.ts +103 -0
  566. package/src/workspace/capabilities-contract.test.ts +4 -0
  567. package/src/workspace/capabilities-contract.ts +9 -1
  568. package/src/workspace/capabilities.schema.json +98 -0
  569. package/src/workspace/change-set.schema.json +42 -3
  570. package/src/workspace/ci-cloud-identity.ts +31 -1
  571. package/src/workspace/compose-graph.test.ts +12 -0
  572. package/src/workspace/compose-graph.ts +12 -1
  573. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  574. package/src/workspace/conformance/index.ts +6 -1
  575. package/src/workspace/drift-cli.test.ts +65 -0
  576. package/src/workspace/drift-cli.ts +365 -0
  577. package/src/workspace/drift.schema.json +672 -0
  578. package/src/workspace/graph-cli.ts +5 -0
  579. package/src/workspace/graph.schema.json +72 -0
  580. package/src/workspace/init.test.ts +38 -1
  581. package/src/workspace/init.ts +50 -8
  582. package/src/workspace/intent-gaps.test.ts +18 -2
  583. package/src/workspace/ledger.schema.json +1 -1
  584. package/src/workspace/nested-graph.ts +1 -0
  585. package/src/workspace/pr-report.schema.json +76 -3
  586. package/src/workspace/promote-waves.test.ts +296 -0
  587. package/src/workspace/promote-waves.ts +340 -0
  588. package/src/workspace/promote.test.ts +158 -4
  589. package/src/workspace/promote.ts +398 -57
  590. package/src/workspace/reason-codes.test.ts +4 -1
  591. package/src/workspace/reason-codes.ts +14 -0
  592. package/src/workspace/search-contract.ts +2 -0
  593. package/src/workspace/search.schema.json +103 -5
@@ -1,9 +1,10 @@
1
1
  import { lexiconNames } from "../../lexicon-module";
2
- import { resolve } from "node:path";
2
+ import { basename, resolve } from "node:path";
3
3
  import { commandBuildParams } from "../build-params-cli";
4
4
  import { build } from "../../build";
5
5
  import { takeSnapshot } from "../../lifecycle/snapshot";
6
- import { readSnapshot, readSnapshotAt, readEnvironmentSnapshots, listSnapshots, fetchLifecycle, pushLifecycle, snapshotStorageKey, StaleLifecycleBranchError } from "../../lifecycle/git";
6
+ import { snapshotStorageKey, StaleLifecycleBranchError } from "../../lifecycle/git";
7
+ import { readEnvironmentSnapshotsFrom, resolveSnapshotStore, type SnapshotStore } from "../../lifecycle/snapshot-store";
7
8
  import { deepDiffForLexicon, type DeclaredEntities } from "../../lifecycle/deep-observe";
8
9
  import { countUnclaimed, countPending, countPropertyDrift, countHeld, suspiciousHeld, type DeepDiffResult, type DeepEntityHeld } from "../../lifecycle/deep-diff";
9
10
  import { describePathOrigin, getPathProvenance, getProvenance, type EntityProvenance } from "../../provenance";
@@ -13,12 +14,14 @@ import {
13
14
  baselineForLexicon,
14
15
  emptyBaseline,
15
16
  readObservationBaseline,
16
- writeObservationBaseline,
17
- OBSERVATION_BASELINE_FILE,
17
+ saveObservationBaseline,
18
18
  type DeviationToAccept,
19
19
  type ObservationBaseline,
20
20
  } from "../../lifecycle/observation-baseline";
21
21
  import { computeBuildDigest, declaredDefinitions, diffDigests } from "../../lifecycle/digest";
22
+ import { diffDeclaredAgainstIndex, indexDriftDocument, type IndexDriftResult } from "../../lifecycle/index-drift";
23
+ import { joinCell, parseIndexRef, readIndexRef, type IndexRead } from "../../lifecycle/index-join";
24
+ import { CHANT_VERSION } from "../../chant-version";
22
25
  import { diffLive, diffLiveArtifacts, diffSnapshots, missingRow, type LiveDiffResult, type LiveArtifactDiffResult, type SnapshotDiffResult } from "../../lifecycle/live-diff";
23
26
  import { buildChangeSet, renderChangeSet, renderChangeSetMarkdown, gitlabMrReport, unobservedPlanNotice, type ChangeSet, type HeldEntitySet } from "../../lifecycle/change-set";
24
27
  import { mergeReceiptEntries, observedValueResolver, planReceipts, readReceiptValue, type ReceiptReading } from "../../lifecycle/receipt-plan";
@@ -53,6 +56,9 @@ import type { BuildResult } from "../../build";
53
56
  import type { ParsedArgs } from "../registry";
54
57
  import type { ChantConfig } from "../../config";
55
58
 
59
+ /** The config fields the observation baseline reads (#4519). */
60
+ type LoadedChantConfigLike = { observationBaseline?: unknown; snapshots?: unknown };
61
+
56
62
  /**
57
63
  * Resolve the build root for a lifecycle command. The project root (where
58
64
  * chant.config.ts lives) is always ".", but the *build* can be scoped to a
@@ -146,6 +152,7 @@ export async function runLifecycleSnapshot(ctx: CommandContext): Promise<number>
146
152
  // from its own scoped root — so its logical ids match what it deploys — and
147
153
  // snapshots against its own live stack name (#932).
148
154
  const targets = resolveStackTargets(args, config);
155
+ const store = await resolveSnapshotStore({ cwd: projectPath, config });
149
156
  let anySnapshotSaved = false;
150
157
  let anyHardError = false;
151
158
 
@@ -188,6 +195,7 @@ export async function runLifecycleSnapshot(ctx: CommandContext): Promise<number>
188
195
  deep: args.deep,
189
196
  ambient: args.ambient,
190
197
  ambientKinds: projectKinds,
198
+ store,
191
199
  });
192
200
  } catch (err) {
193
201
  if (err instanceof StaleLifecycleBranchError) {
@@ -213,7 +221,10 @@ export async function runLifecycleSnapshot(ctx: CommandContext): Promise<number>
213
221
  const counts = result.snapshots
214
222
  .map((s) => `${s.lexicon}(${Object.keys(s.resources).length})`)
215
223
  .join(" ");
216
- console.error(formatSuccess(`${prefix}Snapshot saved to chant/lifecycle (${counts})`));
224
+ // `none` keeps nothing, and says so: the observation still ran (#4519).
225
+ console.error(result.store && !result.store.written
226
+ ? formatWarning({ message: `${prefix}Snapshot observed, not kept: the store is ${result.store.where} (${counts})` })
227
+ : formatSuccess(`${prefix}Snapshot saved to ${store.where} (${counts})`));
217
228
  }
218
229
  if (result.errors.length > 0) anyHardError = true;
219
230
  }
@@ -237,10 +248,11 @@ export async function runLifecycleShow(ctx: CommandContext): Promise<number> {
237
248
  }
238
249
 
239
250
  // Fetch from remote first
240
- await fetchLifecycle();
251
+ const store = await resolveSnapshotStore();
252
+ await store.fetch();
241
253
 
242
254
  if (lexiconFilter) {
243
- const content = await readSnapshot(environment, lexiconFilter);
255
+ const content = await store.readLatest(environment, lexiconFilter);
244
256
  if (!content) {
245
257
  console.error(formatError({ message: `No snapshot found for ${environment}/${lexiconFilter}` }));
246
258
  return 1;
@@ -249,7 +261,7 @@ export async function runLifecycleShow(ctx: CommandContext): Promise<number> {
249
261
  const snapshot: LifecycleSnapshot = JSON.parse(content);
250
262
  printSnapshotTable(snapshot);
251
263
  } else {
252
- const snapshots = await readEnvironmentSnapshots(environment);
264
+ const snapshots = await readEnvironmentSnapshotsFrom(store, environment);
253
265
  if (snapshots.size === 0) {
254
266
  console.error(formatError({ message: `No snapshots found for environment "${environment}"` }));
255
267
  return 1;
@@ -337,6 +349,22 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
337
349
  });
338
350
  }
339
351
 
352
+ // `--index <ref>` (#4526): the observed side is an index version in the
353
+ // store, not a live read and not a snapshot.
354
+ const indexMode = args.index !== undefined;
355
+ if (indexMode && (args.live || (args.betweenA && args.betweenB))) {
356
+ console.error(formatError({
357
+ message: `chant lifecycle diff takes --index or ${args.live ? "--live" : "--between"}, not both`,
358
+ hint: "--index compares source with an index version in the store; --live reads the estate now; --between compares two snapshots",
359
+ }));
360
+ return 1;
361
+ }
362
+ const indexRef = indexMode ? parseIndexRef(String(args.index)) : undefined;
363
+ if (indexRef && "error" in indexRef) {
364
+ console.error(formatError({ message: indexRef.error, hint: indexRef.hint }));
365
+ return 1;
366
+ }
367
+
340
368
  // Filter serializers to target lexicon before building
341
369
  const targetSerializers = lexiconFilter
342
370
  ? plugins.filter((p) => p.name === lexiconFilter).map((p) => p.serializer)
@@ -348,7 +376,19 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
348
376
  // the caller asked for rather than the parameter defaults (#1483).
349
377
  const declaredParams = await commandBuildParams(config.buildParams, args);
350
378
  if (!declaredParams) return 1;
351
- await fetchLifecycle();
379
+ const store = await resolveSnapshotStore({ config });
380
+ await store.fetch();
381
+
382
+ let indexRead: Extract<IndexRead, { doc: unknown }> | undefined;
383
+ if (indexRef && !("error" in indexRef)) {
384
+ const read = await readIndexRef(store, indexRef);
385
+ if ("error" in read) {
386
+ console.error(formatError({ message: read.error, ...(read.hint ? { hint: read.hint } : {}) }));
387
+ return 1;
388
+ }
389
+ indexRead = read;
390
+ }
391
+ const indexResults: IndexDriftResult[] = [];
352
392
 
353
393
  // One target per stack (single-stack projects: exactly one), each built from
354
394
  // its own scoped root and diffed against its own live stack name (#932).
@@ -364,7 +404,7 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
364
404
  // Accepted-deviation baseline (#1014). Read once for the whole run — it is
365
405
  // env-keyed, not stack-keyed, and every deep pass subtracts from the same
366
406
  // committed set. Absent is the normal state (nothing accepted yet).
367
- const baseline = args.live ? await readObservationBaseline(environment) : null;
407
+ const baseline = args.live || indexRead ? await readObservationBaseline(environment, { config }) : null;
368
408
  const accepted: Record<string, DeviationToAccept[]> = {};
369
409
  // Effect receipts across every target's build (#1833) — `--update-baseline`
370
410
  // refuses to accept drift on them (the effect step is their only writer),
@@ -375,7 +415,7 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
375
415
  // Floci), so `--live` is self-sufficient even when the ambient shell never
376
416
  // exported e.g. AWS_ENDPOINT_URL. Ambient always wins when it's already set.
377
417
  // Scoped to just the live reads below — restored in `finally`.
378
- const liveLexicons = args.live ? plugins.filter((p) => p.describeResources || p.listArtifacts) : [];
418
+ const liveLexicons = args.live && !indexRead ? plugins.filter((p) => p.describeResources || p.listArtifacts) : [];
379
419
  const endpointResult = applyLiveEndpoint(config.environments, environment, liveLexicons);
380
420
  if (endpointResult.notice) console.error(formatWarning({ message: endpointResult.notice }));
381
421
 
@@ -440,6 +480,7 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
440
480
  plugins,
441
481
  buildResult,
442
482
  json,
483
+ store,
443
484
  stack: target.stack,
444
485
  region: target.region,
445
486
  componentStacks,
@@ -461,16 +502,66 @@ export async function runLifecycleDiff(ctx: CommandContext): Promise<number> {
461
502
  if (target.stack) perStackJson[target.stack] = r.byLexicon;
462
503
  else combinedLexiconsJson = r.byLexicon;
463
504
  }
505
+ } else if (indexRead) {
506
+ const r = runLifecycleDiffIndex({
507
+ read: indexRead,
508
+ lexicons: lexiconFilter ? [lexiconFilter] : builtLexicons,
509
+ plugins,
510
+ buildResult,
511
+ baseline,
512
+ member: basename(projectRoot),
513
+ stack: target.stack,
514
+ region: target.region,
515
+ json,
516
+ projectRoot,
517
+ });
518
+ indexResults.push(r);
519
+ for (const [lexicon, part] of Object.entries(r.byLexicon)) {
520
+ if (args.updateBaseline) (accepted[lexicon] ??= []).push(...deviationsToAccept(part.deep));
521
+ }
522
+ if (args.updateBaseline) {
523
+ for (const name of collectEffectReceipts(buildResult.entities).keys()) receiptEntities.add(name);
524
+ }
464
525
  } else {
465
- await runLifecycleDiffDigest({ environment, lexicons, buildResult, stack: target.stack });
526
+ await runLifecycleDiffDigest({ environment, lexicons, buildResult, stack: target.stack, store });
466
527
  }
467
528
  }
468
529
 
469
530
  // `--update-baseline` (#1014): record what the deep pass just reported as
470
531
  // accepted, so it stops re-alerting. Runs before the summary lines so the
471
532
  // "no drift" verdict below still describes the run that produced it.
472
- if (args.live && args.updateBaseline) {
473
- await recordAcceptedBaseline(environment, baseline, accepted, json, receiptEntities);
533
+ if ((args.live || indexRead) && args.updateBaseline) {
534
+ await recordAcceptedBaseline(environment, baseline, accepted, json, receiptEntities, config);
535
+ }
536
+
537
+ if (indexRead) {
538
+ // Drift records, drift document v1 (#4526).
539
+ if (json) console.log(JSON.stringify(indexDriftDocument(indexResults, CHANT_VERSION)));
540
+ else {
541
+ const sum = (k: keyof IndexDriftResult["counts"]) => indexResults.reduce((n, r) => n + r.counts[k], 0);
542
+ const ref = indexResults[0]?.ref;
543
+ const declaredCount = sum("declared");
544
+ const drift = sum("properties") + sum("pending");
545
+ const notObserved = sum("notObserved");
546
+ if (anyBuildError) {
547
+ // the build error is already printed
548
+ } else if (declaredCount === 0) {
549
+ console.error(formatWarning({ message: "No declared entities to compare with the index" }));
550
+ } else if (drift === 0) {
551
+ console.error(
552
+ notObserved > 0
553
+ ? formatWarning({
554
+ message: `No drift detected against index ${ref} across ${sum("bound")} bound entity(ies), but ${notObserved} declared entity(ies) are not observed by it: that part of the estate is unknown, not clean`,
555
+ })
556
+ : formatSuccess(`No drift detected against index ${ref} across ${declaredCount} declared entity(ies)`),
557
+ );
558
+ } else {
559
+ console.error(formatWarning({
560
+ message: `Drift against index ${ref}: ${sum("properties")} property difference(s) on ${sum("drifted")} entity(ies), ${sum("pending")} declared entity(ies) absent from it, ${notObserved} not observed`,
561
+ }));
562
+ }
563
+ }
564
+ return anyBuildError ? 1 : 0;
474
565
  }
475
566
 
476
567
  if (args.live) {
@@ -523,6 +614,7 @@ async function recordAcceptedBaseline(
523
614
  accepted: Record<string, DeviationToAccept[]>,
524
615
  json: boolean,
525
616
  receipts?: ReadonlySet<string>,
617
+ config?: ChantConfig | LoadedChantConfigLike,
526
618
  ): Promise<void> {
527
619
  const total = Object.values(accepted).reduce((n, d) => n + d.length, 0);
528
620
  if (total === 0) {
@@ -541,13 +633,16 @@ async function recordAcceptedBaseline(
541
633
  for (const [lexicon, deviations] of Object.entries(accepted)) {
542
634
  next = acceptDeviations(next, lexicon, deviations, { receipts });
543
635
  }
544
- await writeObservationBaseline(next);
545
- const pushed = await pushLifecycle();
636
+ // A file under .chant/observation-baseline/ by default (#4519): commit it
637
+ // with the change that needed it, so the acceptance is reviewed.
638
+ const saved = await saveObservationBaseline(next, config ? { config } : undefined);
546
639
  if (!json) {
547
- console.error(formatSuccess(
548
- `--update-baseline: accepted ${total} deviation(s) into ${environment}/${OBSERVATION_BASELINE_FILE} on chant/lifecycle` +
549
- (pushed ? " (pushed)" : " (local only — no remote configured or push refused)"),
550
- ));
640
+ const note =
641
+ saved.published === "pushed" ? " (pushed)"
642
+ : saved.published === "file" ? " (commit it to keep it)"
643
+ : saved.published === "none" ? " (the baseline store is none: nothing kept)"
644
+ : " (local only — no remote configured or push refused)";
645
+ console.error(formatSuccess(`--update-baseline: accepted ${total} deviation(s) into ${saved.where}${note}`));
551
646
  }
552
647
  } catch (err) {
553
648
  console.error(formatError({
@@ -571,8 +666,9 @@ interface BetweenDiffArgs {
571
666
  * two-way delta per lexicon.
572
667
  */
573
668
  async function runLifecycleDiffBetween(args: BetweenDiffArgs): Promise<number> {
574
- await fetchLifecycle();
575
669
  const { config } = await loadChantConfig(resolve("."));
670
+ const store = await resolveSnapshotStore({ config });
671
+ await store.fetch();
576
672
  const lexicons = args.lexiconFilter ? [args.lexiconFilter] : lexiconNames(config.lexicons ?? []);
577
673
  if (lexicons.length === 0) {
578
674
  console.error(formatError({ message: "No lexicons to diff — pass a lexicon or declare `lexicons` in chant.config." }));
@@ -590,8 +686,8 @@ async function runLifecycleDiffBetween(args: BetweenDiffArgs): Promise<number> {
590
686
 
591
687
  const byLexicon: Record<string, SnapshotDiffResult> = {};
592
688
  for (const lexicon of lexicons) {
593
- const prev = parseResources(await readSnapshotAt(args.environment, lexicon, args.refA));
594
- const next = parseResources(await readSnapshotAt(args.environment, lexicon, args.refB));
689
+ const prev = parseResources(await store.readAt(args.environment, lexicon, args.refA));
690
+ const next = parseResources(await store.readAt(args.environment, lexicon, args.refB));
595
691
  if (prev === null && next === null) {
596
692
  if (!args.json) console.error(formatWarning({ message: `${lexicon}: not captured at either ref — skipping` }));
597
693
  continue;
@@ -625,6 +721,8 @@ interface DigestDiffArgs {
625
721
  environment: string;
626
722
  lexicons: string[];
627
723
  buildResult: BuildResult;
724
+ /** Where the previous digest is read (#4519). */
725
+ store: SnapshotStore;
628
726
  /** Deployed stack name for a multi-stack project (#932); scopes the snapshot read. */
629
727
  stack?: string;
630
728
  }
@@ -634,7 +732,7 @@ async function runLifecycleDiffDigest(args: DigestDiffArgs): Promise<number> {
634
732
  if (args.stack) console.log(`\n${formatBold(`■ stack ${args.stack}`)}`);
635
733
 
636
734
  for (const lexicon of args.lexicons) {
637
- const content = await readSnapshot(args.environment, snapshotStorageKey(lexicon, args.stack));
735
+ const content = await args.store.readLatest(args.environment, snapshotStorageKey(lexicon, args.stack));
638
736
  let previousDigest = undefined;
639
737
  if (content) {
640
738
  const snapshot: LifecycleSnapshot = JSON.parse(content);
@@ -679,6 +777,8 @@ interface LiveDiffArgs {
679
777
  lexicons: string[];
680
778
  plugins: ObservationLexicon[];
681
779
  buildResult: BuildResult;
780
+ /** Where the "then" side is read (#4519). */
781
+ store: SnapshotStore;
682
782
  /** Emit machine-readable JSON on stdout instead of the human report (#852). */
683
783
  json: boolean;
684
784
  /** Deployed stack name for a multi-stack project (#932); scopes the live
@@ -857,7 +957,7 @@ async function runLifecycleDiffLive(args: LiveDiffArgs): Promise<LiveDiffOutcome
857
957
 
858
958
  // Read previous snapshot once; both flows pull what they need.
859
959
  let prevSnapshot: LifecycleSnapshot | undefined;
860
- const content = await readSnapshot(args.environment, snapshotStorageKey(lexiconName, args.stack));
960
+ const content = await args.store.readLatest(args.environment, snapshotStorageKey(lexiconName, args.stack));
861
961
  if (content) prevSnapshot = JSON.parse(content);
862
962
 
863
963
  let lexiconChecked = false;
@@ -971,6 +1071,76 @@ async function runLifecycleDiffLive(args: LiveDiffArgs): Promise<LiveDiffOutcome
971
1071
  return { byLexicon, totalDrift, totalUnobserved, totalLexiconsChecked, toAccept };
972
1072
  }
973
1073
 
1074
+ interface IndexDiffArgs {
1075
+ read: Extract<IndexRead, { doc: unknown }>;
1076
+ lexicons: string[];
1077
+ plugins: CommandContext["plugins"];
1078
+ buildResult: BuildResult;
1079
+ baseline: ObservationBaseline | null;
1080
+ member: string;
1081
+ stack?: string;
1082
+ region?: string;
1083
+ json: boolean;
1084
+ projectRoot: string;
1085
+ }
1086
+
1087
+ /**
1088
+ * `chant lifecycle diff <env> --index <ref>` for one stack (#4526): every
1089
+ * declared entity joined to the index version, the bound ones deep-diffed
1090
+ * against their indexed trees, the rest reported with their join verdict.
1091
+ * Reads nothing but the store. See ../../lifecycle/index-drift.ts.
1092
+ */
1093
+ function runLifecycleDiffIndex(args: IndexDiffArgs): IndexDriftResult {
1094
+ const entities = new Map<string, DeclaredEntities>();
1095
+ for (const lexicon of args.lexicons) entities.set(lexicon, new Map());
1096
+ for (const [name, entity] of args.buildResult.entities) {
1097
+ const forLexicon = entities.get(entity.lexicon);
1098
+ if (!forLexicon || !isObservableDeclarable(entity)) continue;
1099
+ const pathOrigins = getPathProvenance(entity);
1100
+ forLexicon.set(name, {
1101
+ entityType: entity.entityType,
1102
+ props: (entity.props != null ? entity.props : {}) as Record<string, unknown>,
1103
+ ...(pathOrigins ? { pathOrigins } : {}),
1104
+ });
1105
+ }
1106
+ const result = diffDeclaredAgainstIndex({
1107
+ doc: args.read.doc,
1108
+ version: args.read.version,
1109
+ entities,
1110
+ lexicons: args.plugins,
1111
+ baseline: args.baseline,
1112
+ member: args.member,
1113
+ ...(args.stack ? { stack: args.stack } : {}),
1114
+ ...(args.region ? { region: args.region } : {}),
1115
+ chant: CHANT_VERSION,
1116
+ });
1117
+ if (args.json) return result;
1118
+
1119
+ const { stamp } = args.read.doc;
1120
+ const by = stamp.source.version ? `${stamp.source.name} ${stamp.source.version}` : stamp.source.name;
1121
+ if (args.stack) console.log(`\n${formatBold(`■ stack ${args.stack}`)}`);
1122
+ console.log(`index ${result.ref}, read ${stamp.observedAt} by ${by}`);
1123
+ const provenanceOf = (name: string): EntityProvenance | undefined => {
1124
+ const entity = args.buildResult.entities.get(name);
1125
+ return entity ? getProvenance(entity) : undefined;
1126
+ };
1127
+ for (const [lexicon, part] of Object.entries(result.byLexicon)) {
1128
+ const rows = Object.entries(part.joins).sort(([a], [b]) => a.localeCompare(b));
1129
+ const bound = rows.filter(([, j]) => j.verdict === "bound").length;
1130
+ console.log(formatBold(`\n${lexicon}: ${bound}/${rows.length} declared entity(ies) bound to the index`));
1131
+ const unbound = rows.filter(([, j]) => j.verdict !== "bound");
1132
+ if (unbound.length > 0) {
1133
+ console.log(formatBold("\nNOT COMPARED (the join did not bind; never diffed):"));
1134
+ for (const [name, j] of unbound) {
1135
+ const detail = "detail" in j ? `: ${j.detail}` : j.verdict === "absent" ? ": the index listed its type and location and does not hold it" : "";
1136
+ console.log(` ? ${name} ${joinCell(j) ?? ""}${detail}`);
1137
+ }
1138
+ }
1139
+ renderDeepDiff(lexicon, part.deep, provenanceOf, args.projectRoot);
1140
+ }
1141
+ return result;
1142
+ }
1143
+
974
1144
  /**
975
1145
  * Everything a deep diff reported this run, as deviations to record accepted.
976
1146
  * `--update-baseline` accepts what was *reported*, never what was already
@@ -1264,7 +1434,8 @@ export async function runLifecyclePlan(ctx: CommandContext): Promise<number> {
1264
1434
  return 1;
1265
1435
  }
1266
1436
 
1267
- await fetchLifecycle();
1437
+ const store = await resolveSnapshotStore({ config });
1438
+ await store.fetch();
1268
1439
 
1269
1440
  const lexicons = lexiconFilter ? [lexiconFilter] : Array.from(buildResult.manifest.lexicons);
1270
1441
 
@@ -1424,7 +1595,8 @@ export async function runLifecyclePlan(ctx: CommandContext): Promise<number> {
1424
1595
  }
1425
1596
  }
1426
1597
 
1427
- const content = await readSnapshot(environment, lexiconName);
1598
+ // Evidence only (#4519): the change set never makes a decision from it.
1599
+ const content = await store.readLatest(environment, lexiconName);
1428
1600
  const observedThen = content ? (JSON.parse(content) as LifecycleSnapshot).resources : undefined;
1429
1601
 
1430
1602
  const cs = buildChangeSet(environment, {
@@ -1745,17 +1917,20 @@ async function promptProdTeardown(environment: string): Promise<boolean> {
1745
1917
  export async function runLifecycleLog(ctx: CommandContext): Promise<number> {
1746
1918
  const environment = ctx.args.extraPositional;
1747
1919
 
1748
- await fetchLifecycle();
1920
+ const store = await resolveSnapshotStore();
1921
+ await store.fetch();
1749
1922
 
1750
- const entries = await listSnapshots({ environment });
1923
+ const entries = await store.list({ ...(environment ? { environment } : {}) });
1751
1924
  if (entries.length === 0) {
1752
1925
  console.error(formatError({ message: "No state snapshots found" }));
1753
1926
  return 1;
1754
1927
  }
1755
1928
 
1929
+ // A git store's version is a commit; s3's an object version, local's a time.
1930
+ const isCommit = (v: string) => /^[0-9a-f]{40}$/.test(v);
1756
1931
  for (const entry of entries) {
1757
1932
  const date = entry.date.split("T")[0];
1758
- console.log(`${entry.commit.slice(0, 7)} ${date} ${entry.message}`);
1933
+ console.log(`${isCommit(entry.version) ? entry.version.slice(0, 7) : entry.version} ${date} ${entry.key ?? entry.message}`);
1759
1934
  }
1760
1935
 
1761
1936
  return 0;
@@ -3,6 +3,8 @@ import { describeCommand, printDescribeResult } from "../commands/describe";
3
3
  import { importCommand, importFromContent, importFromLive, importFromLiveStacks, printImportResult } from "../commands/import";
4
4
  import { loadChantConfig } from "../../config";
5
5
  import { resolve } from "path";
6
+ import { existsSync, readFileSync } from "fs";
7
+ import type { IndexImportSelection } from "../../lifecycle/index-import";
6
8
  import { auditCommand, printAuditResult, type AuditFormat, type AuditTier, type AuditFailOn } from "../commands/audit";
7
9
  import type { ReportTheme } from "../../audit/report-html";
8
10
  import { AGENT_RUNTIMES, AGENT_SCOPES, type AgentRuntime, type AgentScope } from "../../agents/types";
@@ -248,6 +250,55 @@ export async function runDescribe(ctx: CommandContext): Promise<number> {
248
250
  return result.success ? 0 : 1;
249
251
  }
250
252
 
253
+ /**
254
+ * The selection for `chant import --index` (#4524). `--type` and `--name`
255
+ * take comma-separated lists, `--tag key=value` repeats, `--select` is a JSON
256
+ * file of node ids (a list of ids, of `{ id }` or `{ node }` objects, or an
257
+ * object holding `nodes` or `results`) or a comma-separated list of ids, and
258
+ * `--all` takes every node.
259
+ */
260
+ export function indexSelectionFromArgs(args: CommandContext["args"]): IndexImportSelection | Error {
261
+ const list = (v?: string) => (v ? v.split(",").map((s) => s.trim()).filter(Boolean) : undefined);
262
+ const selection: IndexImportSelection = {};
263
+ if (args.all) selection.all = true;
264
+ const types = list(args.selectType);
265
+ if (types) selection.types = types;
266
+ const names = list(args.selectName);
267
+ if (names) selection.names = names;
268
+ if (args.selectTags && args.selectTags.length > 0) {
269
+ const tags: Record<string, string> = {};
270
+ for (const t of args.selectTags) {
271
+ const eq = t.indexOf("=");
272
+ tags[t.slice(0, eq)] = t.slice(eq + 1);
273
+ }
274
+ selection.tags = tags;
275
+ }
276
+ if (args.selectAddress) {
277
+ const path = resolve(args.selectAddress);
278
+ if (existsSync(path)) {
279
+ let parsed: unknown;
280
+ try {
281
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
282
+ } catch (err) {
283
+ return new Error(`--select ${args.selectAddress} is not JSON: ${err instanceof Error ? err.message : String(err)}`);
284
+ }
285
+ const rows = Array.isArray(parsed)
286
+ ? parsed
287
+ : parsed && typeof parsed === "object"
288
+ ? ((parsed as { nodes?: unknown[]; results?: unknown[] }).nodes ?? (parsed as { results?: unknown[] }).results ?? [])
289
+ : [];
290
+ const ids = rows
291
+ .map((r) => (typeof r === "string" ? r : r && typeof r === "object" ? ((r as { id?: unknown }).id ?? (r as { node?: unknown }).node) : undefined))
292
+ .filter((x): x is string => typeof x === "string");
293
+ if (ids.length === 0) return new Error(`--select ${args.selectAddress} names no node ids`);
294
+ selection.nodes = ids;
295
+ } else {
296
+ selection.nodes = list(args.selectAddress);
297
+ }
298
+ }
299
+ return selection;
300
+ }
301
+
251
302
  export async function runImport(ctx: CommandContext): Promise<number> {
252
303
  const { args } = ctx;
253
304
 
@@ -330,6 +381,28 @@ export async function runImport(ctx: CommandContext): Promise<number> {
330
381
  return result.success ? 0 : 1;
331
382
  }
332
383
 
384
+ // `--index <ref>` (#4524): typed source for indexed resources; nothing is applied.
385
+ if (args.index) {
386
+ const { importFromIndex, summarizeIndexImport } = await import("../commands/import-index");
387
+ const selection = indexSelectionFromArgs(args);
388
+ if (selection instanceof Error) {
389
+ console.error(formatError({ message: selection.message }));
390
+ return 1;
391
+ }
392
+ const result = await importFromIndex({
393
+ index: args.index,
394
+ selection,
395
+ output: args.output,
396
+ force: args.force,
397
+ report: args.reportFile,
398
+ adoptOut: args.adoptOut,
399
+ src: args.src,
400
+ });
401
+ printImportResult(result);
402
+ if (result.report) console.error(summarizeIndexImport(result.report));
403
+ return result.success ? 0 : 1;
404
+ }
405
+
333
406
  // `--from <env>` switches import from a template file to a live source.
334
407
  if (args.migrateFrom) {
335
408
  const selector: ResourceSelector | undefined =
@@ -5,6 +5,13 @@
5
5
  * The note lists the blocked set. Git, the change signal, the derivation and
6
6
  * the gate ledger are fakes, as in `pr-waves.test.ts`; the planning, the
7
7
  * coverage check and the apply are the real ones.
8
+ *
9
+ * Kept fast (#4502): the readers pr-plan and pr-apply run against the
10
+ * declaration at base (hooks, policy, security groups, ci.locks, new
11
+ * migrations) each start git processes in this checkout, so they read as a
12
+ * declaration that sets none of them. The pull request is planned once for
13
+ * the file, since every test starts from the same estate and so the same
14
+ * digest; each test approves that digest on its own ledger.
8
15
  */
9
16
 
10
17
  import { mkdtemp, readFile, rm } from "node:fs/promises";
@@ -46,6 +53,28 @@ vi.mock("../../lifecycle/git", async () => {
46
53
  return { ...actual, pushLifecycleReport: async () => { throw new Error("pr-waves.test must not push chant/lifecycle"); } };
47
54
  });
48
55
 
56
+ // The declaration at base sets no hooks, policy, security group or lock, and the change adds no migration (#4502).
57
+ vi.mock("../../components/pr-hooks", async () => {
58
+ const actual = await vi.importActual<typeof import("../../components/pr-hooks")>("../../components/pr-hooks");
59
+ return { ...actual, prLoopHooksAt: (_cwd: string, base: string) => ({ hooks: {}, base }) };
60
+ });
61
+ vi.mock("../../components/pr-policy", async () => {
62
+ const actual = await vi.importActual<typeof import("../../components/pr-policy")>("../../components/pr-policy");
63
+ return { ...actual, prLoopPolicyAt: (_cwd: string, base: string) => ({ base }) };
64
+ });
65
+ vi.mock("../../workspace/security", async () => {
66
+ const actual = await vi.importActual<typeof import("../../workspace/security")>("../../workspace/security");
67
+ return { ...actual, securityGroupsAt: () => new Set() };
68
+ });
69
+ vi.mock("../../workspace/ci-locks", async () => {
70
+ const actual = await vi.importActual<typeof import("../../workspace/ci-locks")>("../../workspace/ci-locks");
71
+ return { ...actual, ciLocksAt: () => null };
72
+ });
73
+ vi.mock("../../workspace/new-migrations", async () => {
74
+ const actual = await vi.importActual<typeof import("../../workspace/new-migrations")>("../../workspace/new-migrations");
75
+ return { ...actual, newMigrationFilesBetween: async () => [] };
76
+ });
77
+
49
78
  vi.mock("./run", () => ({
50
79
  GATED_EXIT_CODE: 3,
51
80
  autoReleaseRun: (id: string) => ({ runId: id, runOrigin: { forge: "local" } }),
@@ -166,10 +195,16 @@ const resolution = (gate: string, planDigest: string, by = "github:alice") => ({
166
195
  const report = async (): Promise<PrReport> => JSON.parse(await readFile(join(dir, "pr-apply.json"), "utf8")) as PrReport;
167
196
  const note = async (): Promise<string> => readFile(join(dir, "pr-note.md"), "utf8");
168
197
 
198
+ /** The digest of pull request 12's plan, planned by the first test that asks: every test starts from the same estate (#4502). */
199
+ let plannedDigest: string | undefined;
200
+
169
201
  /** Plan pull request 12, as its plan job does, and approve its digest as alice. */
170
202
  async function planAndApprove(): Promise<string> {
171
- expect(await runComponentsPrPlan(ctx({ pr: 12 }))).toBe(0);
172
- const digest = (JSON.parse(await readFile(join(dir, "pr-plan.json"), "utf8")) as PrReport).digest;
203
+ if (plannedDigest === undefined) {
204
+ expect(await runComponentsPrPlan(ctx({ pr: 12 }))).toBe(0);
205
+ plannedDigest = (JSON.parse(await readFile(join(dir, "pr-plan.json"), "utf8")) as PrReport).digest;
206
+ }
207
+ const digest = plannedDigest;
173
208
  gateLedger = memoryGateLedgerPort({ resolutions: [resolution("pr-apply", digest)] });
174
209
  errors = [];
175
210
  return digest;
@@ -70,6 +70,7 @@ import { join, relative, resolve, sep } from "node:path";
70
70
  import { loadChantConfig, resolveAutoReleaseDisabled, type ChantConfig } from "../../config";
71
71
  import { resolveCommit, resolveMergeBase } from "../../components/changed-files";
72
72
  import { deriveFanOut, fanOutRegistry } from "../../components/fan-out-support";
73
+ import { findComponentGroups, groupFanOut } from "../../components/groups";
73
74
  import {
74
75
  applyPrSet,
75
76
  approvalOf,
@@ -241,6 +242,7 @@ export async function prepare(ctx: CommandContext, stage: "plan" | "apply" | "re
241
242
  const signal = await resolveChangedUnits(ctx, config, { base, stacksOnly: true });
242
243
  if ("error" in signal) return fail(signal.error, signal.hint);
243
244
 
245
+ const registry = await fanOutRegistry(projectPath, config);
244
246
  let derived;
245
247
  try {
246
248
  derived = await deriveFanOut({
@@ -249,6 +251,7 @@ export async function prepare(ctx: CommandContext, stage: "plan" | "apply" | "re
249
251
  sandbox: args.sandbox,
250
252
  buildParams: paramsResolution.provenance,
251
253
  config,
254
+ registry,
252
255
  });
253
256
  } catch (err) {
254
257
  const message = err instanceof Error ? err.message : String(err);
@@ -288,9 +291,17 @@ export async function prepare(ctx: CommandContext, stage: "plan" | "apply" | "re
288
291
  if (policy.note) console.error(formatInfo(policy.note));
289
292
 
290
293
  const env = args.env ?? "local";
291
- const registry = await fanOutRegistry(projectPath, config);
292
294
  const routedAway = member ? await routing(ctx, stage, projectPath, member, base, forge) : undefined;
293
295
  if (routedAway) console.error(formatInfo(routedAway));
296
+ // ws-107: the groups a capability plans and applies as one, placed in the waves every stage and wave job reads.
297
+ let plan = derived.plan;
298
+ if (!routedAway && plan.order.length > 0) {
299
+ try {
300
+ plan = groupFanOut(plan, derived.components, await findComponentGroups({ components: derived.components, plan, registry, env }));
301
+ } catch (err) {
302
+ return fail(err instanceof Error ? err.message : String(err));
303
+ }
304
+ }
294
305
  return {
295
306
  config,
296
307
  env,
@@ -300,7 +311,7 @@ export async function prepare(ctx: CommandContext, stage: "plan" | "apply" | "re
300
311
  gate: args.gate ?? PR_APPLY_GATE,
301
312
  ...(forge ? { forge } : {}),
302
313
  components: derived.components,
303
- plan: routedAway ? { ...derived.plan, order: [], waves: [], seeds: [], indeterminate: [] } : derived.plan,
314
+ plan: routedAway ? { ...derived.plan, order: [], waves: [], seeds: [], indeterminate: [] } : plan,
304
315
  signal: derived.signal,
305
316
  ...(derived.sites ? { sites: derived.sites } : {}),
306
317
  registry,
@@ -1043,6 +1054,7 @@ async function prApply(ctx: CommandContext, stage: StageEvents, beforeMerge?: Pr
1043
1054
  env: p.env,
1044
1055
  set,
1045
1056
  gate: p.gate,
1057
+ approval,
1046
1058
  decideRound,
1047
1059
  ...(resumed ? { resumed: prior } : {}),
1048
1060
  // The apply is this run's waves, so the fan-out's own run framing is
@@ -1647,6 +1659,7 @@ async function tryResume(p: Prepared, attempt: FanOutAttempt, file: string, op:
1647
1659
  seededOutputs: p.seededOutputs,
1648
1660
  record,
1649
1661
  completed,
1662
+ applied: outputs,
1650
1663
  });
1651
1664
  if (!resumed.ok) return skip(`what is left is not what was approved. ${resumed.reasons.join("; ")}`);
1652
1665
  console.error(formatInfo(`resume : ${completed.join(", ")} applied in an earlier attempt; applying ${resumed.plan.order.join(", ") || "nothing more"} under the approval of ${record.digest}`));
@@ -1967,7 +1980,7 @@ async function prApplyWaves(
1967
1980
  planFailedIn ??= k;
1968
1981
  continue;
1969
1982
  }
1970
- const moved = waveNotCovered(approvedSet.doc, fresh.doc, approvedSet.members);
1983
+ const moved = waveNotCovered(approvedSet.doc, fresh.doc, approvedSet.members, { approved: approvedSet.shapes, fresh: fresh.shapes });
1971
1984
  set = fresh;
1972
1985
  if (moved.length === 0) {
1973
1986
  console.error(formatInfo(`wave ${k}: the approved plan covers what it plans now, so it applies under the pull request's approval`));