@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
@@ -2,7 +2,7 @@ import { isAbsolute, relative, resolve } from "node:path";
2
2
  import { discoverOps } from "../../op/discover";
3
3
  import { discover } from "../../discovery/index";
4
4
  import { partitionByLexicon, computeStackGraph, build } from "../../build";
5
- import { applyLexiconOutputs, buildGraphIr, buildLiveGraphIr, collectUnobserved, overlayGraphs, sourceOverlayGraphs, GRAPH_IR_VERSION, type GraphIR, type IRPipeline, type LexiconWorkspaceOutputs, type LiveObservation } from "../../graph-ir";
5
+ import { applyLexiconOutputs, buildGraphIr, buildLiveGraphIr, collectUnobserved, overlayGraphs, sourceOverlayGraphs, withInstanceNodes, GRAPH_IR_VERSION, type GraphIR, type IRPipeline, type LexiconWorkspaceOutputs, type LiveObservation } from "../../graph-ir";
6
6
  import { behaviourOverlay } from "../../behaviour-overlay";
7
7
  import type { SerializerResult } from "../../serializer";
8
8
  import { applyBehaviourOverlay, behaviourRequestFromIr } from "../../behaviour-graph";
@@ -10,6 +10,7 @@ import { buildDeclaredPerStack } from "../../graph-declared";
10
10
  import { mergeProjectOps } from "../../graph-ops";
11
11
  import { reconstructEdges, mergeCatalogs, containmentGroups, type ReferenceCatalog, type ContainmentPair } from "../../graph-refs";
12
12
  import { carryAnnotations, observeResources } from "../../lifecycle/observe";
13
+ import type { RootRead } from "../../observation";
13
14
  import { withLiveReadSession } from "../../live-read-session";
14
15
  import { replaySnapshots, hasSnapshot } from "../../lifecycle/replay";
15
16
  import { loadChantConfig, environmentNames, matchesDeclaredEnvironment, loadChantConfigUpward, type ChantConfig } from "../../config";
@@ -293,6 +294,8 @@ async function runGraphLive(
293
294
  let observations: LiveObservation[];
294
295
  // Facts about absent entities (#4372), carried onto the overlay's nodes.
295
296
  let annotations: Record<string, Record<string, unknown>> | undefined;
297
+ // Which read answered each root (#4452), carried onto the IR as written.
298
+ let rootReads: Record<string, RootRead> | undefined;
296
299
  // A replay is a graph of what was recorded, so it must not reach the provider
297
300
  // at all — not for observation and not for enrichment. Anything that quietly
298
301
  // called out would make `--at` a live read with an older label on it.
@@ -322,6 +325,7 @@ async function runGraphLive(
322
325
  });
323
326
  observations = observeResult.observations;
324
327
  annotations = observeResult.annotations;
328
+ rootReads = observeResult.rootReads;
325
329
  const { errors, warnings, notes = [] } = observeResult;
326
330
  for (const e of errors) console.error(formatWarning({ message: e }));
327
331
  // Unobserved entities (#1089) arrive as warnings — a node missing from the
@@ -383,7 +387,13 @@ async function runGraphLive(
383
387
  let containment: ContainmentPair[] = [];
384
388
  if (catalogs.length > 0) {
385
389
  const reconstructed = reconstructEdges(ir.nodes, mergeCatalogs(catalogs));
386
- ir = { ...ir, edges: reconstructed.edges };
390
+ // Merge, never replace, as `search --live` does (#1273). An observation
391
+ // can carry edges of its own (terraform's state dependencies, #4520's
392
+ // engine facts), and a catalog on one observing lexicon used to wipe every
393
+ // lexicon's: the reason terraform's catalog stayed off its plugin until
394
+ // #4535.
395
+ const seen = new Set(ir.edges.map((e) => `${e.from}|${e.to}|${e.viaAttr ?? ""}`));
396
+ ir = { ...ir, edges: [...ir.edges, ...reconstructed.edges.filter((e) => !seen.has(`${e.from}|${e.to}|${e.viaAttr ?? ""}`))] };
387
397
  containment = reconstructed.containment;
388
398
  }
389
399
 
@@ -427,7 +437,12 @@ async function runGraphLive(
427
437
  console.error(formatWarning({ message: "overlay: a build root failed to render — showing the provisioned graph without the declared overlay" }));
428
438
  } else {
429
439
  await mergeGraphOps(declared.entities, declared.sourceFiles, projectPath);
430
- const declaredIr = withLexiconEdges(buildGraphIr(declared.entities, projectPath), plugins, declared.entities);
440
+ // Instance nodes (#4454) on the declared side too, so a live instance
441
+ // meets its declared node and an unread one is painted alone.
442
+ const declaredIr = withInstanceNodes(
443
+ withLexiconEdges(buildGraphIr(declared.entities, projectPath), plugins, declared.entities),
444
+ declared.entities,
445
+ );
431
446
  ir =
432
447
  args.overlayAnchor === "live"
433
448
  ? overlayGraphs(ir, declaredIr, overlayOpts)
@@ -484,6 +499,11 @@ async function runGraphLive(
484
499
  }
485
500
  }
486
501
 
502
+ // The structured read report (#4452) goes on last, past the overlay, lens
503
+ // and detail passes that each build a fresh IR, so it is in every format's
504
+ // IR whatever those kept.
505
+ if (rootReads && Object.keys(rootReads).length > 0) ir = { ...ir, rootReads };
506
+
487
507
  return emitIr(ir, ctx, format);
488
508
  }
489
509
 
@@ -938,7 +958,9 @@ async function runGraphView(
938
958
 
939
959
  // Build the base IR, focus with a lens (declarable-level, most precise), then
940
960
  // apply the detail tier — so e.g. blast:<resource> works before any collapse.
941
- let ir: GraphIR = buildGraphIr(result.entities, projectPath);
961
+ // Instance nodes (#4454) go on here, not in `buildGraphIr`, so search and
962
+ // the other readers of the base IR still count one node per block.
963
+ let ir: GraphIR = withInstanceNodes(buildGraphIr(result.entities, projectPath), result.entities);
942
964
 
943
965
  // The declared half of the epic's delta (#2355): this path predicts the
944
966
  // file, the live path predicts the account. Same helper, so the two shapes
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `chant index <substrate>` as a registry command (chant#4532).
3
+ *
4
+ * At the terminal, main() hands `index`'s raw argv straight to
5
+ * `runCloudIndex` (../../lifecycle/cloud-index.ts), because its flags are its
6
+ * own. This entry is for a caller that has already parsed a command line
7
+ * with core's parser (the ACP server asks the registry whether a word is a
8
+ * verb): it rebuilds the words that parser keeps (the substrate, `--json`,
9
+ * `--help`), and refuses when the parsed line carried an action flag core's
10
+ * parser knows, so the read-only rule holds on this path too.
11
+ */
12
+ import type { CommandContext } from "../registry";
13
+
14
+ export async function runIndexCommand(ctx: CommandContext): Promise<number> {
15
+ const { runCloudIndex } = await import("../../lifecycle/cloud-index");
16
+ const a = ctx.args;
17
+ const action = a.fix ? "--fix" : a.live ? "--live" : a.write ? "--write" : a.yes ? "--yes" : a.op ? "--op" : undefined;
18
+ const words = [...(a.positionals ?? (a.path && a.path !== "." ? [a.path] : []))];
19
+ if (words[0] === "diff") {
20
+ // #4533 — `chant index diff <a> [<b>]` reads two stored versions; it takes no action flag either.
21
+ if (action) {
22
+ process.stderr.write(`chant index diff: refused ${action}: the diff only reads the store\n`);
23
+ return 2;
24
+ }
25
+ const { runIndexDiff } = await import("./index-diff");
26
+ return runIndexDiff([...words.slice(1), ...(a.json ? ["--json"] : []), ...(a.help ? ["--help"] : [])]);
27
+ }
28
+ if (action) words.push(action);
29
+ if (a.json) words.push("--json");
30
+ if (a.help) words.push("--help");
31
+ return runCloudIndex(words);
32
+ }
@@ -0,0 +1,68 @@
1
+ // #4533: `chant index diff <a> [<b>]` over a local store holding the two fixture versions of
2
+ // ../../lifecycle/__fixtures__/index-diff/, read through --store local:<dir> as chant index writes it.
3
+ import { copyFileSync, mkdirSync, mkdtempSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { describe, expect, it } from "vitest";
7
+ import { runIndexDiff } from "./index-diff";
8
+
9
+ const FIXTURES = join(import.meta.dirname, "..", "..", "lifecycle", "__fixtures__", "index-diff");
10
+ const V1 = "20261026T120000Z-aaaaaaaaaaaa.json";
11
+ const V2 = "20261026T130000Z-bbbbbbbbbbbb.json";
12
+ const V3 = "20261026T140000Z-cccccccccccc.json";
13
+ const SCOPE = "cloud/aws/000000000000/5c1f0e2a9b7d";
14
+ const OTHER = "cloud/aws/000000000000/0123456789ab";
15
+
16
+ /** A scratch dir (no chant project) with a local store: SCOPE holds V1 and V2, OTHER holds V1's document as V3. */
17
+ function scratch() {
18
+ const cwd = mkdtempSync(join(tmpdir(), "chant-index-diff-"));
19
+ for (const [scope, file, as] of [[SCOPE, V1, V1], [SCOPE, V2, V2], [OTHER, V1, V3]] as const) {
20
+ const dir = join(cwd, "store", "index", ...scope.split("/"));
21
+ mkdirSync(dir, { recursive: true });
22
+ copyFileSync(join(FIXTURES, file), join(dir, as));
23
+ }
24
+ return cwd;
25
+ }
26
+
27
+ async function run(argv: string[]) {
28
+ const cwd = scratch();
29
+ let out = "";
30
+ let err = "";
31
+ const code = await runIndexDiff([...argv, "--store", "local:store"], {
32
+ cwd,
33
+ env: { XDG_DATA_HOME: join(cwd, "none") },
34
+ stdout: (s) => void (out += s),
35
+ stderr: (s) => void (err += s),
36
+ });
37
+ return { code, out, err };
38
+ }
39
+
40
+ describe("chant index diff", () => {
41
+ it("prints the diff of two named versions as JSON", async () => {
42
+ const r = await run([`${SCOPE}/${V1}`, V2, "--json"]);
43
+ expect(r.code).toBe(0);
44
+ expect(JSON.parse(r.out)).toMatchObject({ diff: "v1", scopeKey: SCOPE, from: { version: V1 }, to: { version: V2 }, counts: { properties: 1, added: 1, removed: 1 } });
45
+ });
46
+
47
+ it("a scope alone diffs its last two versions, as text", async () => {
48
+ const r = await run([SCOPE]);
49
+ expect(r.code).toBe(0);
50
+ expect(r.out).toContain(`Index diff: ${SCOPE}`);
51
+ expect(r.out).toContain('attrs.VersioningConfiguration.Status: "Suspended" -> "Enabled"');
52
+ expect(r.out).toContain("(not observed in the to read: read-failed, ListResources returned ThrottlingException)");
53
+ });
54
+
55
+ it("refuses two scopes by name", async () => {
56
+ const r = await run([`${SCOPE}/${V1}`, `${OTHER}/${V3}`, "--json"]);
57
+ expect(r.code).toBe(1);
58
+ expect(JSON.parse(r.out)).toMatchObject({ error: "index-scope-mismatch", fromScopeKey: SCOPE, toScopeKey: OTHER });
59
+ expect(r.err).toContain("different scopes");
60
+ });
61
+
62
+ it("usage errors: no version, an unknown flag", async () => {
63
+ expect((await run([])).code).toBe(2);
64
+ const r = await run([SCOPE, "--apply"]);
65
+ expect(r.code).toBe(2);
66
+ expect(r.err).toContain("unknown flag --apply");
67
+ });
68
+ });
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `chant index diff <a> [<b>] [--store <spec>] [--json]` (#4533): what
3
+ * changed between two versions of one index scope.
4
+ *
5
+ * `<a>` is `<scopeKey>/<version>`, or `<scopeKey>` alone. `<b>` is
6
+ * `<scopeKey>/<version>`, or a version name in `<a>`'s scope. With no `<b>`,
7
+ * the version before `<a>` is diffed against `<a>`, and a scope alone diffs
8
+ * its last two versions.
9
+ *
10
+ * Versions are read through #4519's store API, from the store `chant index`
11
+ * writes to (#4532's {@link planIndexStore}: `--store <spec>`, else the
12
+ * project's store, else the per-user one), so a git-ref, local or s3 store
13
+ * diffs the same way. Nothing is built, no lexicon is loaded and no cloud is
14
+ * queried. The computation is ../../lifecycle/index-diff.ts.
15
+ */
16
+ import { homedir } from "node:os";
17
+ import { chantUserStoreDir, IndexUsageError, planIndexStore, type StorePlan } from "../../lifecycle/cloud-index";
18
+ import { diffIndexVersionRefs, IndexScopeMismatch, NotAnIndexVersion, parseIndexVersionRef, type IndexDiff } from "../../lifecycle/index-diff";
19
+ import { createSnapshotStore, resolveSnapshotStore, snapshotStoreConfig, type SnapshotStore } from "../../lifecycle/snapshot-store";
20
+
21
+ export const INDEX_DIFF_USAGE = `chant index diff <a> [<b>] [--store <spec>] [--json]
22
+
23
+ What changed between two versions of one index scope: nodes added and
24
+ removed, properties changed, edges added and removed. A node is removed only
25
+ where the later read listed its type and location; elsewhere it is not
26
+ observed. Two versions of different scopes are refused.
27
+
28
+ <a> <scopeKey>/<version>, or <scopeKey> for its latest version
29
+ <b> <scopeKey>/<version>, or a version name in <a>'s scope.
30
+ Absent: the version before <a> against <a> (a scope alone:
31
+ its last two versions)
32
+ --store <spec> the store to read, as chant index takes it (local,
33
+ local:<dir>, git-ref, git-ref:<ref>, s3,
34
+ s3://<bucket>[/<prefix>]). Default: the project's store,
35
+ else chant's per-user store
36
+ --json the diff as JSON
37
+ `;
38
+
39
+ export interface IndexDiffRunOptions {
40
+ cwd?: string;
41
+ env?: Record<string, string | undefined>;
42
+ home?: string;
43
+ stdout?: (s: string) => void;
44
+ stderr?: (s: string) => void;
45
+ /** Open the planned store; tests pass a fake. */
46
+ openStore?: (plan: StorePlan, cwd: string) => Promise<Pick<SnapshotStore, "kind" | "listIndex" | "readIndexAt" | "fetch">>;
47
+ }
48
+
49
+ async function openReadStore(plan: StorePlan, cwd: string): Promise<SnapshotStore> {
50
+ switch (plan.kind) {
51
+ case "user":
52
+ return createSnapshotStore({ store: "local", dir: plan.dir }, { cwd });
53
+ case "project":
54
+ return resolveSnapshotStore({ cwd: plan.projectDir });
55
+ case "spec":
56
+ return createSnapshotStore(snapshotStoreConfig(plan.configured, plan.env), { cwd, env: plan.env });
57
+ }
58
+ }
59
+
60
+ const show = (v: unknown) => (v === undefined ? "(absent)" : JSON.stringify(v));
61
+
62
+ /** The diff as text, one line per change. */
63
+ export function renderIndexDiff(d: IndexDiff): string {
64
+ const lines: string[] = [];
65
+ lines.push(`Index diff: ${d.scopeKey}`);
66
+ lines.push(` from ${d.from.version ?? d.from.digest.slice(0, 12)} (observed ${d.from.observedAt})`);
67
+ lines.push(` to ${d.to.version ?? d.to.digest.slice(0, 12)} (observed ${d.to.observedAt})`);
68
+ if (d.unchanged) {
69
+ lines.push("", "Same digest: nothing changed.");
70
+ return `${lines.join("\n")}\n`;
71
+ }
72
+ const c = d.counts;
73
+ lines.push(
74
+ "",
75
+ `${c.added} added, ${c.removed} removed, ${c.changed} changed (${c.properties} properties), ${c.notObserved} not observed; edges ${c.edgesAdded} added, ${c.edgesRemoved} removed, ${c.edgesNotObserved} not observed`,
76
+ );
77
+ for (const n of d.nodes.added) lines.push(` + ${n.key}`);
78
+ for (const n of d.nodes.removed) lines.push(` - ${n.key}`);
79
+ for (const n of d.nodes.changed) {
80
+ lines.push(` ~ ${n.key}`);
81
+ for (const p of n.properties) lines.push(` ${p.path}: ${show(p.before)} -> ${show(p.after)}`);
82
+ }
83
+ for (const n of d.nodes.notObserved) lines.push(` ? ${n.key} (not observed in the ${n.side} read: ${n.verdict}${n.detail ? `, ${n.detail}` : ""})`);
84
+ for (const e of d.edges.added) lines.push(` + edge ${e.from} -> ${e.to}${e.viaAttr ? ` via ${e.viaAttr}` : ""}`);
85
+ for (const e of d.edges.removed) lines.push(` - edge ${e.from} -> ${e.to}${e.viaAttr ? ` via ${e.viaAttr}` : ""}`);
86
+ for (const e of d.edges.notObserved) lines.push(` ? edge ${e.from} -> ${e.to} (${e.unobservedEnd} not observed in the ${e.side} read: ${e.verdict})`);
87
+ for (const v of d.coverage) lines.push(` coverage ${v.listedType} @ ${v.location}: ${v.before ?? "(none)"} -> ${v.after ?? "(none)"}`);
88
+ return `${lines.join("\n")}\n`;
89
+ }
90
+
91
+ /** `chant index diff`'s argv (the words after `diff`). Exit 0 on a diff, 1 on a refusal, 2 on a usage error. */
92
+ export async function runIndexDiff(argv: readonly string[], o: IndexDiffRunOptions = {}): Promise<number> {
93
+ const out = o.stdout ?? ((s: string) => void process.stdout.write(s));
94
+ const err = o.stderr ?? ((s: string) => void process.stderr.write(s));
95
+ const cwd = o.cwd ?? process.cwd();
96
+ const env = o.env ?? process.env;
97
+ if (argv.includes("-h") || argv.includes("--help")) {
98
+ out(INDEX_DIFF_USAGE);
99
+ return 0;
100
+ }
101
+ const refs: string[] = [];
102
+ let store: string | undefined;
103
+ let json = false;
104
+ for (let i = 0; i < argv.length; i++) {
105
+ const a = argv[i]!;
106
+ if (a === "--json") json = true;
107
+ else if (a === "--store") {
108
+ store = argv[++i];
109
+ if (!store || store.startsWith("-")) {
110
+ err(`chant index diff: --store needs a store spec\n\n${INDEX_DIFF_USAGE}`);
111
+ return 2;
112
+ }
113
+ } else if (a.startsWith("--store=")) store = a.slice("--store=".length);
114
+ else if (a.startsWith("-")) {
115
+ err(`chant index diff: unknown flag ${a}\n\n${INDEX_DIFF_USAGE}`);
116
+ return 2;
117
+ } else refs.push(a);
118
+ }
119
+ if (refs.length < 1 || refs.length > 2) {
120
+ err(`chant index diff: name one or two index versions\n\n${INDEX_DIFF_USAGE}`);
121
+ return 2;
122
+ }
123
+
124
+ let d: IndexDiff;
125
+ try {
126
+ const from = parseIndexVersionRef(refs[0]!);
127
+ const to = refs[1] ? parseIndexVersionRef(refs[1], from.scopeKey) : undefined;
128
+ const plan = planIndexStore({ store, cwd, env, defaultStoreDir: () => chantUserStoreDir(env, o.home ?? homedir()), command: "chant index diff" });
129
+ const s = await (o.openStore ?? openReadStore)(plan, cwd);
130
+ await s.fetch();
131
+ d = await diffIndexVersionRefs(s, from, to);
132
+ } catch (e) {
133
+ if (e instanceof IndexUsageError) {
134
+ err(`${e.message}\n`);
135
+ return 2;
136
+ }
137
+ if (e instanceof IndexScopeMismatch || e instanceof NotAnIndexVersion) {
138
+ if (json) out(`${JSON.stringify({ error: e.code, message: e.message, ...(e instanceof IndexScopeMismatch ? { fromScopeKey: e.fromScopeKey, toScopeKey: e.toScopeKey } : {}) }, null, 2)}\n`);
139
+ err(`chant index diff: ${e.message}\n`);
140
+ return 1;
141
+ }
142
+ throw e;
143
+ }
144
+ out(json ? `${JSON.stringify(d, null, 2)}\n` : renderIndexDiff(d));
145
+ return 0;
146
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * #4482 (B4 of #4477): a local `chant run` of a TerraformApplyOp lands on the
3
+ * Op run ledger of the member that declares the Op, and `chant ledger runs`
4
+ * names the member whose directory holds the root it applied in
5
+ * `root_member`. That is the read behold's member run card takes for a
6
+ * choudoufu member applied from a chant one.
7
+ */
8
+ import { spawnSync } from "node:child_process";
9
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { dirname, join } from "node:path";
12
+ import { afterAll, beforeEach, describe, expect, it } from "vitest";
13
+ import type { ActivityFn, ActivityProfile } from "../../op/activity-registry";
14
+ import { OpRunFailure, runOpLocally } from "../../op/local-executor";
15
+ import { withKnownOutputs } from "../../op/activity-failure";
16
+ import type { OpConfig } from "../../op/types";
17
+ import { clearMemberLedgerCache } from "../../lifecycle/member-ledger";
18
+ import { readLedgerTable } from "../../lifecycle/ledger-read";
19
+ import { queryLedgers } from "./query";
20
+
21
+ const scratch: string[] = [];
22
+ afterAll(() => {
23
+ for (const d of scratch) rmSync(d, { recursive: true, force: true });
24
+ });
25
+ beforeEach(() => clearMemberLedgerCache());
26
+
27
+ function git(args: string[], cwd: string): void {
28
+ const r = spawnSync("git", args, { cwd, encoding: "utf-8" });
29
+ if (r.status !== 0) throw new Error(`git ${args.join(" ")}: ${r.stderr}`);
30
+ }
31
+
32
+ function workspace(): string {
33
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-root-member-")));
34
+ scratch.push(root);
35
+ const files: Record<string, string> = {
36
+ "chant.workspace.json": JSON.stringify({
37
+ name: "estate",
38
+ schema: 1,
39
+ members: [
40
+ { name: "app", dir: "app", kind: "chant" },
41
+ { name: "aws", dir: "estates/aws", kind: "choudoufu" },
42
+ ],
43
+ }),
44
+ "app/chant.config.json": JSON.stringify({ lexicons: ["terraform"], terraform: { roots: { aws: { dir: "../estates/aws" } } } }),
45
+ "estates/aws/main.tf": "",
46
+ };
47
+ for (const [path, text] of Object.entries(files)) {
48
+ mkdirSync(dirname(join(root, path)), { recursive: true });
49
+ writeFileSync(join(root, path), text);
50
+ }
51
+ git(["init", "-q", "-b", "main"], root);
52
+ git(["config", "user.email", "test@chant.dev"], root);
53
+ git(["config", "user.name", "Test"], root);
54
+ git(["add", "-A"], root);
55
+ git(["commit", "-q", "-m", "init"], root);
56
+ return root;
57
+ }
58
+
59
+ const PROFILES: Record<string, ActivityProfile> = { single: { timeout: "5m", retry: { maximumAttempts: 1 } } };
60
+
61
+ // The phases TerraformApplyOp builds with `gate: "never"`, with the Init step's #4482 outcome.
62
+ const config: OpConfig = {
63
+ name: "aws-apply",
64
+ overview: "",
65
+ labels: { Apply: "true", TerraformRoot: "aws" },
66
+ phases: [
67
+ { name: "Init", steps: [{ kind: "activity", fn: "terraformInit", args: { root: "aws" }, outcomeAttribute: { name: "TerraformRootPath", from: "rootPath" } }] },
68
+ { name: "Plan", steps: [{ kind: "activity", fn: "terraformPlan", args: { root: "aws" } }] },
69
+ { name: "Apply", steps: [{ kind: "activity", fn: "terraformApply", args: { root: "aws" } }] },
70
+ ],
71
+ };
72
+
73
+ describe("chant ledger runs: root_member (#4482)", () => {
74
+ it("lists a local TerraformApplyOp run under the member whose root it applied", async () => {
75
+ const root = workspace();
76
+ const activities = new Map<string, ActivityFn>([
77
+ // terraformInit's own result: the root's directory relative to the workspace.
78
+ ["terraformInit", async () => ({ dir: join(root, "estates/aws"), rootPath: "estates/aws" })],
79
+ ["terraformPlan", async () => ({ changed: true })],
80
+ ["terraformApply", async () => ({ applied: true })],
81
+ ]);
82
+ const result = await runOpLocally(config, activities, PROFILES, undefined, { ledger: { cwd: join(root, "app") } });
83
+ expect(result.status).toBe("ok");
84
+
85
+ const doc = await readLedgerTable("runs", () => queryLedgers(root), { where: ["root_member=aws"] });
86
+ if ("error" in doc) throw new Error(doc.error.message);
87
+ expect(doc.rows).toHaveLength(1);
88
+ expect(doc.rows[0]).toMatchObject({
89
+ member: "app",
90
+ op: "aws-apply",
91
+ status: "ok",
92
+ root_member: "aws",
93
+ labels: { Apply: "true", TerraformRoot: "aws" },
94
+ outcomes: { TerraformRootPath: "estates/aws" },
95
+ });
96
+ });
97
+
98
+ it("leaves root_member null for a run with no root path", async () => {
99
+ const root = workspace();
100
+ const plain: OpConfig = { name: "plain", overview: "", phases: [{ name: "Only", steps: [{ kind: "activity", fn: "noop" }] }] };
101
+ await runOpLocally(plain, new Map<string, ActivityFn>([["noop", async () => undefined]]), PROFILES, undefined, { ledger: { cwd: join(root, "app") } });
102
+ const doc = await readLedgerTable("runs", () => queryLedgers(root), {});
103
+ if ("error" in doc) throw new Error(doc.error.message);
104
+ expect(doc.rows.map((r) => [r.op, r.root_member])).toEqual([["plain", null]]);
105
+ });
106
+
107
+ it("names root_member on a failed run when Init knew the root before it failed (#4512)", async () => {
108
+ const root = workspace();
109
+ const activities = new Map<string, ActivityFn>([
110
+ // What terraformInit throws when `terraform init` itself fails: the root resolved first.
111
+ ["terraformInit", async () => {
112
+ throw withKnownOutputs(new Error("terraform init: backend unreachable"), { dir: join(root, "estates/aws"), rootPath: "estates/aws" });
113
+ }],
114
+ ["terraformPlan", async () => ({ changed: true })],
115
+ ["terraformApply", async () => ({ applied: true })],
116
+ ]);
117
+ const failure = await runOpLocally(config, activities, PROFILES, undefined, { ledger: { cwd: join(root, "app") } }).then(
118
+ () => undefined,
119
+ (err: unknown) => err,
120
+ );
121
+ expect(failure).toBeInstanceOf(OpRunFailure);
122
+ expect((failure as OpRunFailure).result.status).toBe("fail");
123
+
124
+ const doc = await readLedgerTable("runs", () => queryLedgers(root), { where: ["root_member=aws"] });
125
+ if ("error" in doc) throw new Error(doc.error.message);
126
+ expect(doc.rows).toHaveLength(1);
127
+ expect(doc.rows[0]).toMatchObject({
128
+ member: "app",
129
+ op: "aws-apply",
130
+ status: "fail",
131
+ root_member: "aws",
132
+ labels: { Apply: "true", TerraformRoot: "aws" },
133
+ outcomes: { TerraformRootPath: "estates/aws" },
134
+ });
135
+ });
136
+
137
+ it("leaves root_member null, meaning unknown, when Init fails before it knows the root, with the declared root key in labels (#4508)", async () => {
138
+ const root = workspace();
139
+ const activities = new Map<string, ActivityFn>([
140
+ // Failing before the root resolved (a chant.config.ts that throws), so no known outputs.
141
+ ["terraformInit", async () => {
142
+ throw new Error("chant.config.ts: evaluation failed");
143
+ }],
144
+ ["terraformPlan", async () => ({ changed: true })],
145
+ ["terraformApply", async () => ({ applied: true })],
146
+ ]);
147
+ // A failed run throws OpRunFailure, carrying the result; the ledger row is written first.
148
+ const failure = await runOpLocally(config, activities, PROFILES, undefined, { ledger: { cwd: join(root, "app") } }).then(
149
+ () => undefined,
150
+ (err: unknown) => err,
151
+ );
152
+ expect(failure).toBeInstanceOf(OpRunFailure);
153
+ expect((failure as OpRunFailure).result.status).not.toBe("ok");
154
+
155
+ const doc = await readLedgerTable("runs", () => queryLedgers(root), {});
156
+ if ("error" in doc) throw new Error(doc.error.message);
157
+ expect(doc.rows).toHaveLength(1);
158
+ expect(doc.rows[0]).toMatchObject({ member: "app", op: "aws-apply", root_member: null, labels: { Apply: "true", TerraformRoot: "aws" } });
159
+ expect(doc.rows[0].outcomes).not.toHaveProperty("TerraformRootPath");
160
+ });
161
+ });
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `chant lifecycle drift [--member <name>] [--read <read>] [--resource <addr>]`
3
+ * (behold#995): the project's drift, as each lexicon's own engine reads it,
4
+ * printed as the drift-record NDJSON stream (`../../drift-record.ts`).
5
+ *
6
+ * This is the member half of `chant workspace drift`. A `terraform` or
7
+ * `choudoufu` member has no chant config of its own, so the workspace writes
8
+ * it a reader project (`../../workspace/kind-readers.ts`) and runs this
9
+ * command there, under the member's own toolchain, the way `workspace graph`
10
+ * runs `chant graph`. Every lexicon with a `readDrift` is asked; the lines
11
+ * are printed in the order they came, one JSON object per line. chant core
12
+ * never imports a lexicon to read drift.
13
+ *
14
+ * `--member` names the workspace member the records are for; it defaults to
15
+ * the project directory's name. A lexicon that refuses the read (a read its
16
+ * engine has not got, or one resource on an engine that cannot read one on
17
+ * its own) makes the command print `{"refused": {code, message}}` as its
18
+ * only stdout line and exit 3, so the workspace command can name the
19
+ * refusal. No lexicon with a drift read is the refusal `no-drift-read`.
20
+ */
21
+
22
+ import { basename, resolve } from "node:path";
23
+ import { loadChantConfig } from "../../config";
24
+ import { DRIFT_READS, encodeDriftLine, type DriftLine, type DriftRead, type ReadDriftRefusalCode } from "../../drift-record";
25
+ import { formatError } from "../format";
26
+ import type { CommandContext } from "../registry";
27
+
28
+ /** The exit status of a refused read. */
29
+ export const DRIFT_REFUSED_EXIT = 3;
30
+
31
+ const USAGE = "chant lifecycle drift [--member <name>] [--read <state|plan|refresh|live-read|ownership>] [--resource <addr>]";
32
+
33
+ function refuse(code: ReadDriftRefusalCode, message: string): number {
34
+ console.log(JSON.stringify({ refused: { code, message } }));
35
+ return DRIFT_REFUSED_EXIT;
36
+ }
37
+
38
+ export async function runLifecycleDrift(ctx: CommandContext): Promise<number> {
39
+ const { args, plugins } = ctx;
40
+ const read = args.driftRead;
41
+ if (read !== undefined && !(DRIFT_READS as readonly string[]).includes(read)) {
42
+ console.error(formatError({ message: `--read ${read} is not a drift read; the reads are ${DRIFT_READS.join(", ")}`, hint: USAGE }));
43
+ return 1;
44
+ }
45
+ const projectRoot = resolve(".");
46
+ const { config } = await loadChantConfig(projectRoot);
47
+ const member = args.members?.[0] ?? basename(projectRoot);
48
+ const readers = plugins.filter((p) => typeof p.readDrift === "function");
49
+ if (readers.length === 0) {
50
+ return refuse("no-drift-read", `no lexicon this project declares (${plugins.map((p) => p.name).join(", ") || "none"}) has a drift read`);
51
+ }
52
+ const observedAt = new Date().toISOString();
53
+ const lines: DriftLine[] = [];
54
+ for (const plugin of readers) {
55
+ const answer = await plugin.readDrift!({
56
+ member,
57
+ projectRoot,
58
+ config: config as Record<string, unknown>,
59
+ ...(read !== undefined ? { read: read as DriftRead } : {}),
60
+ ...(args.resource !== undefined ? { resource: args.resource } : {}),
61
+ observedAt,
62
+ });
63
+ if ("refused" in answer) return refuse(answer.refused.code, `${plugin.name}: ${answer.refused.message}`);
64
+ lines.push(...answer.lines);
65
+ }
66
+ for (const line of lines) process.stdout.write(encodeDriftLine(line));
67
+ return 0;
68
+ }
@@ -45,6 +45,27 @@ vi.mock("../../lifecycle/git", () => ({
45
45
  readBlobFromPath: (...args: unknown[]) => readBlobFromPathMock(...args),
46
46
  writeBlobToPath: (...args: unknown[]) => writeBlobToPathMock(...args),
47
47
  }));
48
+ // Snapshots are read through the index store (#4519); this one hands each read to the mocks above.
49
+ vi.mock("../../lifecycle/snapshot-store", async () => {
50
+ const { fakeSnapshotStore } = await import("../../lifecycle/snapshot-store-fake");
51
+ const store = fakeSnapshotStore({
52
+ readLatest: async (environment, key) => (await readSnapshotMock(environment, key)) as string | null,
53
+ list: async (opts) => ((await listSnapshotsMock(opts)) as Array<{ commit: string; date: string; message: string }>).map((e) => ({ version: e.commit, date: e.date, message: e.message })),
54
+ fetch: async () => {
55
+ await fetchLifecycleMock();
56
+ },
57
+ });
58
+ return {
59
+ resolveSnapshotStore: async () => store,
60
+ readEnvironmentSnapshotsFrom: async (_store: unknown, environment: string) => readEnvironmentSnapshotsMock(environment),
61
+ };
62
+ });
63
+ // The baseline is a file under .chant/ (#4519); the write is mocked so a test never writes into the checkout.
64
+ const saveObservationBaselineMock = vi.fn();
65
+ vi.mock("../../lifecycle/observation-baseline", async (importOriginal) => ({
66
+ ...(await importOriginal<typeof import("../../lifecycle/observation-baseline")>()),
67
+ saveObservationBaseline: (...args: unknown[]) => saveObservationBaselineMock(...args),
68
+ }));
48
69
  vi.mock("../../lifecycle/snapshot", () => ({
49
70
  takeSnapshot: (...args: unknown[]) => takeSnapshotMock(...args),
50
71
  }));
@@ -138,6 +159,8 @@ describe("runLifecycleDiff --live", () => {
138
159
  readBlobFromPathMock.mockResolvedValue(null); // no accepted baseline recorded
139
160
  writeBlobToPathMock.mockReset();
140
161
  writeBlobToPathMock.mockResolvedValue("sha");
162
+ saveObservationBaselineMock.mockReset();
163
+ saveObservationBaselineMock.mockResolvedValue({ where: ".chant/observation-baseline/prod.json", published: "file" });
141
164
  pushLifecycleMock.mockReset();
142
165
  pushLifecycleMock.mockResolvedValue(true);
143
166
  });
@@ -715,27 +738,26 @@ describe("runLifecycleDiff --live", () => {
715
738
  expect(output).toContain("could not be observed — that part of the estate is unknown, not clean");
716
739
  });
717
740
 
718
- test("--update-baseline writes what was reported and pushes it", async () => {
741
+ test("--update-baseline writes what was reported to the baseline file", async () => {
719
742
  await runDiff([withDeep()], { updateBaseline: true });
720
- expect(writeBlobToPathMock).toHaveBeenCalledTimes(1);
721
- const [environment, filename, content] = writeBlobToPathMock.mock.calls[0] as [string, string, string];
722
- expect(environment).toBe("prod");
723
- expect(filename).toBe("observation-baseline.json");
724
- const written = JSON.parse(content) as {
725
- lexicons: { aws: { bucket: { accepted: Array<{ path: string; value: unknown }> } } };
726
- };
743
+ expect(saveObservationBaselineMock).toHaveBeenCalledTimes(1);
744
+ const [written] = saveObservationBaselineMock.mock.calls[0] as [
745
+ { environment: string; lexicons: { aws: { bucket: { accepted: Array<{ path: string; value: unknown }> } } } },
746
+ ];
747
+ expect(written.environment).toBe("prod");
727
748
  // Only drift is accepted. A held field needs no acceptance: it was never
728
749
  // reported as a deviation to begin with (#2160).
729
750
  expect(written.lexicons.aws.bucket.accepted.map((a) => a.path)).toEqual(["Versioning"]);
730
- expect(pushLifecycleMock).toHaveBeenCalled();
731
- expect(stderrBuf.join("\n")).toContain("accepted 1 deviation(s)");
751
+ // Never on the orphan branch again (#4519).
752
+ expect(writeBlobToPathMock).not.toHaveBeenCalled();
753
+ expect(stderrBuf.join("\n")).toContain("accepted 1 deviation(s) into .chant/observation-baseline/prod.json");
732
754
  });
733
755
 
734
756
  test("--update-baseline with nothing reported writes nothing", async () => {
735
757
  await runDiff([
736
758
  withDeep({ observeResourcesDeep: staticDeepObservation({}) }),
737
759
  ], { updateBaseline: true });
738
- expect(writeBlobToPathMock).not.toHaveBeenCalled();
760
+ expect(saveObservationBaselineMock).not.toHaveBeenCalled();
739
761
  expect(stderrBuf.join("\n")).toContain("nothing to accept");
740
762
  });
741
763
  });