@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
@@ -0,0 +1,187 @@
1
+ /**
2
+ * The index is never load-bearing (#4519, rule 1).
3
+ *
4
+ * Every snapshot and every other index is derived and safe to lose: removing
5
+ * the store costs history and the cost of observing again, never a decision.
6
+ * Two checks hold that:
7
+ *
8
+ * 1. Behaviour. `buildChangeSet`, which `lifecycle plan` feeds from the store
9
+ * (`observedThen`), proposes the same create, delete, adopt, runtime and
10
+ * unobserved entries, with the same ownership, whether the last snapshot is
11
+ * there, absent (store `none`), or says something else entirely. The only
12
+ * thing a snapshot may move is evidence: an `update` row's deltas are drift
13
+ * since the last read, and with no last read there is none to show.
14
+ * A BREAK that lets the snapshot decide a delete is caught by the same
15
+ * check, which the second test proves.
16
+ * 2. Imports. The modules that decide what to mutate (the change set, the
17
+ * unobserved gate, teardown, apply, the gate and its decision) do not load
18
+ * the store, the snapshot writer or the replay, so they cannot read an index.
19
+ */
20
+ import { describe, expect, test } from "vitest";
21
+ import { existsSync, readFileSync } from "node:fs";
22
+ import { dirname, join, relative, resolve } from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+ import { buildChangeSet, type ChangeSet } from "./change-set";
25
+ import { createSnapshotStore } from "./snapshot-store";
26
+ import { fakeSnapshotStore } from "./snapshot-store-fake";
27
+ import type { DiffLiveInput } from "./live-diff";
28
+ import type { ResourceMetadata } from "../lexicon";
29
+ import type { SnapshotStore } from "./snapshot-store";
30
+
31
+ const meta = (over: Partial<ResourceMetadata> = {}): ResourceMetadata => ({ type: "Fake::Resource", status: "OK", ...over });
32
+
33
+ type Build = (env: string, input: DiffLiveInput) => ChangeSet;
34
+
35
+ /** The live side of each case. The snapshot side is varied below. */
36
+ const cases: Array<{ name: string; input: Omit<DiffLiveInput, "observedThen"> }> = [
37
+ {
38
+ name: "declared and live, declared and absent, owned orphan, foreign orphan",
39
+ input: {
40
+ declared: new Set(["kept", "missing"]),
41
+ observedNow: {
42
+ kept: meta({ ownership: "owned" }),
43
+ orphanOwned: meta({ ownership: "owned" }),
44
+ orphanForeign: meta({ ownership: "foreign" }),
45
+ orphanUnknown: meta(),
46
+ },
47
+ },
48
+ },
49
+ {
50
+ name: "a runtime child and an unobserved declaration",
51
+ input: {
52
+ declared: new Set(["owner", "hole"]),
53
+ observedNow: {
54
+ owner: meta({ ownership: "owned" }),
55
+ child: meta({ ownerChain: { root: "declared", entity: "owner" } } as Partial<ResourceMetadata>),
56
+ },
57
+ unobserved: { hole: { type: "Fake::Resource", reason: "no-credentials" } },
58
+ },
59
+ },
60
+ ];
61
+
62
+ /** Snapshots that disagree with the live read in every way a snapshot can. */
63
+ const snapshotsFor = (input: Omit<DiffLiveInput, "observedThen">): Array<Record<string, ResourceMetadata> | undefined> => {
64
+ const names = [...new Set([...input.declared, ...Object.keys(input.observedNow), "gone"])];
65
+ return [
66
+ undefined,
67
+ {},
68
+ Object.fromEntries(names.map((n) => [n, meta({ ownership: "owned", status: "STALE" })])),
69
+ Object.fromEntries(names.map((n) => [n, meta({ ownership: "foreign", status: "OTHER", physicalId: `old-${n}` })])),
70
+ ];
71
+ };
72
+
73
+ const DECISIONS = new Set(["create", "delete", "adopt", "runtime", "unobserved", "effect"]);
74
+
75
+ /**
76
+ * Every way `build` lets a snapshot change a decision, as text, empty when
77
+ * none does. A name only the snapshot knows is never a decision.
78
+ */
79
+ function loadBearing(build: Build): string[] {
80
+ const found: string[] = [];
81
+ for (const c of cases) {
82
+ const without = build("prod", { ...c.input, observedThen: undefined });
83
+ const decided = new Map(without.entries.map((e) => [e.name, e]));
84
+ for (const then of snapshotsFor(c.input)) {
85
+ const withSnap = build("prod", { ...c.input, observedThen: then });
86
+ for (const e of withSnap.entries) {
87
+ const base = decided.get(e.name);
88
+ if (!base) {
89
+ if (e.action !== "noop") found.push(`${c.name}: ${e.name} exists only in the snapshot and became ${e.action}`);
90
+ continue;
91
+ }
92
+ if (e.ownership !== base.ownership) found.push(`${c.name}: ${e.name} ownership ${base.ownership} -> ${e.ownership}`);
93
+ const decisionMoved = (DECISIONS.has(e.action) || DECISIONS.has(base.action)) && e.action !== base.action;
94
+ if (decisionMoved) found.push(`${c.name}: ${e.name} ${base.action} -> ${e.action}`);
95
+ // Drift since the last read is the one thing a snapshot adds, and only between update and noop.
96
+ if (!decisionMoved && e.action !== base.action && !(base.action === "noop" && e.action === "update")) {
97
+ found.push(`${c.name}: ${e.name} ${base.action} -> ${e.action}`);
98
+ }
99
+ }
100
+ }
101
+ }
102
+ return found;
103
+ }
104
+
105
+ describe("the index is never load-bearing", () => {
106
+ test("no snapshot, an empty one or a contradicting one changes a decision", () => {
107
+ expect(loadBearing(buildChangeSet)).toEqual([]);
108
+ });
109
+
110
+ test("a BREAK that lets the snapshot decide a delete is caught", () => {
111
+ // The break: an undeclared resource the last snapshot called owned is deleted.
112
+ const broken: Build = (env, input) => {
113
+ const cs = buildChangeSet(env, input);
114
+ return {
115
+ ...cs,
116
+ entries: cs.entries.map((e) =>
117
+ e.action === "adopt" && input.observedThen?.[e.name]?.ownership === "owned" ? { ...e, action: "delete" as const, ownership: "owned" as const } : e,
118
+ ),
119
+ };
120
+ };
121
+ expect(loadBearing(broken).some((f) => f.includes("adopt -> delete"))).toBe(true);
122
+ });
123
+
124
+ test("lifecycle plan's read through the store: store none and a populated store decide alike", async () => {
125
+ const input = cases[0].input;
126
+ const populated: SnapshotStore = fakeSnapshotStore({
127
+ readLatest: async () => JSON.stringify({ resources: snapshotsFor(input)[2] }),
128
+ });
129
+ const none = createSnapshotStore({ store: "none" });
130
+ // What runLifecyclePlan does with the store's answer.
131
+ const plan = async (store: SnapshotStore) => {
132
+ const content = await store.readLatest("prod", "fake");
133
+ const observedThen = content ? (JSON.parse(content) as { resources: Record<string, ResourceMetadata> }).resources : undefined;
134
+ return buildChangeSet("prod", { ...input, observedThen });
135
+ };
136
+ const decisions = (cs: ChangeSet) => cs.entries.filter((e) => DECISIONS.has(e.action)).map((e) => `${e.name}:${e.action}:${e.ownership}`);
137
+ expect(decisions(await plan(populated))).toEqual(decisions(await plan(none)));
138
+ });
139
+ });
140
+
141
+ describe("the modules that decide what to mutate never load the index store", () => {
142
+ const SRC = fileURLToPath(new URL("..", import.meta.url));
143
+ const FORBIDDEN = /lifecycle\/(snapshot-store|snapshot-store-s3|snapshot|replay)\.ts$/;
144
+ const DECIDERS = [
145
+ "lifecycle/change-set.ts",
146
+ "lifecycle/unobserved-gate.ts",
147
+ "lifecycle/teardown.ts",
148
+ "lifecycle/green-apply.ts",
149
+ "apply.ts",
150
+ "op/gate.ts",
151
+ "gate-decision.ts",
152
+ ];
153
+ // Runtime imports only: `import type`, `export type` and `import("...")` in a type position load nothing.
154
+ const STATIC = /(?:^|\n)\s*(?:import|export)\s+(?!type\s)[^;]*?from\s+["'](\.[^"']+)["']/g;
155
+ const DYNAMIC = /await\s+import\(\s*["'](\.[^"']+)["']\s*\)/g;
156
+
157
+ function resolveSpec(from: string, spec: string): string | null {
158
+ const base = resolve(dirname(from), spec);
159
+ for (const c of [base, `${base}.ts`, join(base, "index.ts"), base.replace(/\.js$/, ".ts")]) {
160
+ if (c.endsWith(".ts") && existsSync(c)) return c;
161
+ }
162
+ return null;
163
+ }
164
+
165
+ test.each(DECIDERS)("%s", (entry) => {
166
+ const start = join(SRC, entry);
167
+ const parent = new Map<string, string | null>([[start, null]]);
168
+ const queue = [start];
169
+ while (queue.length > 0) {
170
+ const file = queue.shift()!;
171
+ const text = readFileSync(file, "utf8");
172
+ for (const m of [...text.matchAll(STATIC), ...text.matchAll(DYNAMIC)]) {
173
+ const target = resolveSpec(file, m[1]);
174
+ if (target && !parent.has(target)) {
175
+ parent.set(target, file);
176
+ queue.push(target);
177
+ }
178
+ }
179
+ }
180
+ const reached = [...parent.keys()].filter((f) => FORBIDDEN.test(f)).map((f) => {
181
+ const chain: string[] = [];
182
+ for (let c: string | null | undefined = f; c; c = parent.get(c)) chain.push(relative(SRC, c));
183
+ return chain.reverse().join(" -> ");
184
+ });
185
+ expect(reached).toEqual([]);
186
+ });
187
+ });
@@ -2,6 +2,10 @@ export * from "./types";
2
2
  export * from "./git";
3
3
  export * from "./digest";
4
4
  export * from "./snapshot";
5
+ export * from "./snapshot-store";
6
+ export * from "./snapshot-store-s3";
7
+ export * from "./index-document";
8
+ export * from "./index-diff";
5
9
  export * from "./live-diff";
6
10
  export * from "./deep-diff";
7
11
  export * from "./deep-observe";
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { afterAll, beforeEach, describe, expect, test } from "vitest";
12
12
  import { spawnSync } from "node:child_process";
13
- import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
13
+ import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
14
14
  import { tmpdir } from "node:os";
15
15
  import { dirname, join } from "node:path";
16
16
  import { clearMemberLedgerCache, memberLedgerPrefix, MemberLedgerError, resolveMemberLedger } from "./member-ledger";
@@ -192,19 +192,37 @@ describe("every lifecycle store writes under the member path (#2538)", () => {
192
192
  const web = join(root, "services/web");
193
193
  await writeSnapshot("prod", "k8s", '{"from":"api"}', { cwd: api });
194
194
  await writeSnapshot("prod", "k8s", '{"from":"web"}', { cwd: web });
195
- await writeObservationBaseline(emptyBaseline("prod"), { cwd: api });
195
+ // Written from a subdirectory of the member: the baseline still lands under the member.
196
+ await writeObservationBaseline(emptyBaseline("prod"), { cwd: join(api, "src") });
196
197
 
197
198
  const files = branchFiles(root);
198
- expect(files).toContain("_members/api/prod/k8s.json");
199
- expect(files).toContain("_members/web/prod/k8s.json");
200
- expect(files.filter((f) => f.startsWith("_members/api/prod/") && f !== "_members/api/prod/k8s.json")).toHaveLength(1);
201
- expect(files.every((f) => f.startsWith("_members/"))).toBe(true);
199
+ expect(files).toEqual(["_members/api/prod/k8s.json", "_members/web/prod/k8s.json"]);
202
200
  expect([...(await readEnvironmentSnapshots("prod", { cwd: web })).values()]).toEqual(['{"from":"web"}']);
201
+ // The baseline's home is a file under the project since #4519
202
+ // (.chant/observation-baseline/<env>.json), and the project is the member's directory.
203
+ expect(existsSync(join(api, ".chant/observation-baseline/prod.json"))).toBe(true);
204
+ expect(existsSync(join(api, "src/.chant"))).toBe(false);
205
+ expect(existsSync(join(web, ".chant"))).toBe(false);
206
+ expect(existsSync(join(root, ".chant"))).toBe(false);
203
207
  expect(await readObservationBaseline("prod", { cwd: api })).toMatchObject({ environment: "prod" });
204
208
  expect(await readObservationBaseline("prod", { cwd: web })).toBeNull();
209
+ expect(await readObservationBaseline("prod", { cwd: root })).toBeNull();
205
210
  // A member's history lists only the commits that touched its ledger.
206
211
  expect(await listSnapshots({ cwd: web })).toHaveLength(1);
207
- expect(await listSnapshots({ cwd: root })).toHaveLength(3);
212
+ expect(await listSnapshots({ cwd: root })).toHaveLength(2);
213
+ });
214
+
215
+ test("an observation baseline on the git-ref home is under the member path", async () => {
216
+ const root = workspace();
217
+ const api = join(root, "services/api");
218
+ const web = join(root, "services/web");
219
+ const store = { store: "git-ref" as const };
220
+ await writeObservationBaseline(emptyBaseline("prod"), { cwd: api, store });
221
+ const onRef = git(["ls-tree", "-r", "--name-only", "refs/chant/observation-baseline"], root).split("\n").filter(Boolean);
222
+ expect(onRef).toEqual(["_members/api/prod.json"]);
223
+ expect(await readObservationBaseline("prod", { cwd: api, store })).toMatchObject({ environment: "prod" });
224
+ expect(await readObservationBaseline("prod", { cwd: web, store })).toBeNull();
225
+ expect(await readObservationBaseline("prod", { cwd: root, store })).toBeNull();
208
226
  });
209
227
 
210
228
  test("runs and converge records", async () => {
@@ -1,5 +1,14 @@
1
- import { describe, test, expect } from "vitest";
1
+ import { afterEach, describe, test, expect } from "vitest";
2
+ import { execFileSync } from "node:child_process";
3
+ import { existsSync, mkdtempSync, readFileSync as readFile, realpathSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { writeBlobToPath } from "./git";
2
7
  import {
8
+ observationBaselineStoreConfig,
9
+ readObservationBaseline,
10
+ saveObservationBaseline,
11
+ OBSERVATION_BASELINE_FILE,
3
12
  acceptDeviations,
4
13
  acceptedDeviation,
5
14
  baselineForLexicon,
@@ -143,3 +152,65 @@ describe("acceptDeviations refuses effect receipts (#1833)", () => {
143
152
  expect(baselineForLexicon(b, "aws").Role.accepted).toHaveLength(1);
144
153
  });
145
154
  });
155
+
156
+ // ── Where it lives (#4519) ──────────────────────────────────────────────────
157
+
158
+ describe("the baseline's home", () => {
159
+ const dirs: string[] = [];
160
+ afterEach(() => {
161
+ for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
162
+ });
163
+ const repo = (): string => {
164
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "baseline-home-")));
165
+ dirs.push(dir);
166
+ const git = (args: string[]) => execFileSync("git", args, { cwd: dir, encoding: "utf8" });
167
+ git(["init", "-q", "-b", "main"]);
168
+ git(["config", "user.email", "test@chant.dev"]);
169
+ git(["config", "user.name", "Test"]);
170
+ writeFileSync(join(dir, "README.md"), "fixture\n");
171
+ git(["add", "README.md"]);
172
+ git(["commit", "-q", "-m", "init"]);
173
+ return dir;
174
+ };
175
+ const accepted = () =>
176
+ acceptDeviations(emptyBaseline("prod"), "aws", [{ entity: "Assets", path: "Tags[0].Value", value: "platform" }], { now: "2026-10-26T00:00:00.000Z" });
177
+
178
+ test("the default is a file under .chant/observation-baseline/, written for review and never on the orphan branch", async () => {
179
+ const cwd = repo();
180
+ const saved = await saveObservationBaseline(accepted(), { cwd, config: {} });
181
+ expect(saved).toMatchObject({ where: join(".chant", "observation-baseline", "prod.json"), published: "file" });
182
+ const file = join(cwd, ".chant", "observation-baseline", "prod.json");
183
+ expect(parseBaseline(readFile(file, "utf8"))).toEqual(accepted());
184
+ expect(await readObservationBaseline("prod", { cwd, config: {} })).toEqual(accepted());
185
+ expect(() => execFileSync("git", ["rev-parse", "--verify", "-q", "refs/heads/chant/lifecycle"], { cwd })).toThrow();
186
+ });
187
+
188
+ test("a baseline written on chant/lifecycle before #4519 is still read when no file holds one", async () => {
189
+ const cwd = repo();
190
+ await writeBlobToPath("prod", OBSERVATION_BASELINE_FILE, serializeBaseline(accepted()), "legacy", { cwd });
191
+ expect(await readObservationBaseline("prod", { cwd, config: {} })).toEqual(accepted());
192
+ });
193
+
194
+ test("none keeps nothing and reads nothing", async () => {
195
+ const cwd = repo();
196
+ const store = { store: "none" as const };
197
+ expect((await saveObservationBaseline(accepted(), { cwd, store })).published).toBe("none");
198
+ expect(existsSync(join(cwd, ".chant"))).toBe(false);
199
+ expect(await readObservationBaseline("prod", { cwd, store })).toBeNull();
200
+ });
201
+
202
+ test("git-ref writes refs/chant/observation-baseline and reads it back", async () => {
203
+ const cwd = repo();
204
+ const store = { store: "git-ref" as const };
205
+ const saved = await saveObservationBaseline(accepted(), { cwd, store });
206
+ expect(saved.where).toBe("refs/chant/observation-baseline:prod.json");
207
+ expect(saved.published).toBe("local");
208
+ expect(await readObservationBaseline("prod", { cwd, store })).toEqual(accepted());
209
+ });
210
+
211
+ test("the environment overrides chant.config.ts", () => {
212
+ expect(observationBaselineStoreConfig(undefined, {})).toEqual({ store: "file" });
213
+ expect(observationBaselineStoreConfig({ store: "git-ref" }, { CHANT_OBSERVATION_BASELINE_STORE: "none" })).toEqual({ store: "none" });
214
+ expect(() => observationBaselineStoreConfig({ store: "branch" }, {})).toThrow(/not one of/);
215
+ });
216
+ });
@@ -23,25 +23,40 @@
23
23
  *
24
24
  * ## Where it lives
25
25
  *
26
- * `<environment>/observation-baseline.json` on the `chant/lifecycle` orphan
27
- * branch — the epic's named candidate home, and the same storage the snapshots
28
- * (`<env>/<lexicon>.json`), the release ledger (`<env>/releases.jsonl`) and the
29
- * build archive (`_builds/<digest>.json`) already use, through the same
30
- * `writeBlobToPath`/`readBlobFromPath` plumbing. One env-keyed namespace for
31
- * everything chant records *about* an environment rather than *for* it.
26
+ * A file in the repository, `.chant/observation-baseline/<env>.json` under
27
+ * the project (#4519). An acceptance is then a commit in an ordinary pull
28
+ * request, reviewed with the change that needed it. It is not derivable and
29
+ * not critical, and it is meant to be reviewed: that is the rule that puts a
30
+ * record here rather than in the index store.
32
31
  *
33
- * The parse/serialize/update half below is pure and storage-free, so the
34
- * decision is one function call deep if a repo-committed file (`.chant/`) turns
35
- * out to be the better review surface.
32
+ * Other homes, through `observationBaseline` in chant.config.ts or
33
+ * `CHANT_OBSERVATION_BASELINE_STORE`: `git-ref` (`refs/chant/observation-baseline`),
34
+ * `s3` (`<prefix>/observation-baseline/<env>.json`, with the s3 settings of
35
+ * `observationBaseline.s3`, else of `snapshots`), or `none`.
36
+ *
37
+ * A baseline written before #4519, `<environment>/observation-baseline.json`
38
+ * on the `chant/lifecycle` orphan branch, is still read when the configured
39
+ * home has none. It is never written there again.
40
+ *
41
+ * The parse/serialize/update half below is pure and storage-free.
36
42
  */
37
43
 
38
- import { readBlobFromPath, writeBlobToPath } from "./git";
44
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
45
+ import { dirname, join, resolve } from "node:path";
46
+ import { ledgerDir, readBlobFromPath } from "./git";
47
+ import { encodeIndexSegment } from "./index-document";
39
48
  import { sortedJsonReplacer } from "../utils";
40
49
  import { EFFECT_RECEIPT_ENTITY_TYPE } from "../effect-receipt";
41
50
 
42
- /** The file name under `<environment>/` on the orphan branch. */
51
+ /** The file name under `<environment>/` on the orphan branch, where a baseline written before #4519 is read from. */
43
52
  export const OBSERVATION_BASELINE_FILE = "observation-baseline.json";
44
53
 
54
+ /** The directory, relative to the project, that holds one baseline file per environment (#4519). */
55
+ export const OBSERVATION_BASELINE_DIR = ".chant/observation-baseline";
56
+
57
+ /** The ref a `git-ref` baseline is written to. */
58
+ export const OBSERVATION_BASELINE_REF = "refs/chant/observation-baseline";
59
+
45
60
  /** One deviation somebody accepted, bound to the value they accepted. */
46
61
  export interface AcceptedDeviation {
47
62
  /** Property path within the entity's normalized tree (`Tags[0].Value`, `Policy.Statement[1].Effect`). */
@@ -219,31 +234,179 @@ export function countAccepted(baseline: ObservationBaseline | null | undefined):
219
234
  return n;
220
235
  }
221
236
 
222
- // ── Storage (chant/lifecycle orphan branch) ─────────────────────────────────
237
+ // ── Storage ─────────────────────────────────────────────────────────────────
238
+
239
+ export type ObservationBaselineStoreKind = "file" | "git-ref" | "s3" | "none";
240
+
241
+ /** `observationBaseline` in chant.config.ts. */
242
+ export interface ObservationBaselineStoreConfig {
243
+ store: ObservationBaselineStoreKind;
244
+ /** `file`: the directory, relative to the project. Default {@link OBSERVATION_BASELINE_DIR}. */
245
+ dir?: string;
246
+ /** `git-ref`: the ref. Default {@link OBSERVATION_BASELINE_REF}. */
247
+ ref?: string;
248
+ /** `s3`: where. Default: the `snapshots` store's s3 settings. */
249
+ s3?: import("./snapshot-store-s3").S3StoreSettings;
250
+ }
251
+
252
+ const BASELINE_KINDS: readonly ObservationBaselineStoreKind[] = ["file", "git-ref", "s3", "none"];
253
+
254
+ /** Where to read and write a baseline: the project directory, and its config when the caller has it loaded. */
255
+ export interface ObservationBaselineLocation {
256
+ cwd?: string;
257
+ /** The project's loaded chant.config. Loaded from `cwd` upward when absent. */
258
+ config?: { observationBaseline?: unknown; snapshots?: unknown };
259
+ /** Overrides the configured home. */
260
+ store?: ObservationBaselineStoreConfig;
261
+ }
262
+
263
+ /** The home in effect: `CHANT_OBSERVATION_BASELINE_STORE` over `observationBaseline` over `file`. */
264
+ export function observationBaselineStoreConfig(
265
+ configured?: unknown,
266
+ env: Record<string, string | undefined> = process.env,
267
+ ): ObservationBaselineStoreConfig {
268
+ const base = (configured && typeof configured === "object" ? configured : {}) as Partial<ObservationBaselineStoreConfig>;
269
+ const kind = (env.CHANT_OBSERVATION_BASELINE_STORE?.trim() || base.store || "file") as ObservationBaselineStoreKind;
270
+ if (!BASELINE_KINDS.includes(kind)) {
271
+ throw new Error(`observation baseline store "${kind}" is not one of ${BASELINE_KINDS.join(", ")}`);
272
+ }
273
+ return { ...(base.store === kind ? base : {}), store: kind };
274
+ }
275
+
276
+ async function resolveLocation(
277
+ opts?: ObservationBaselineLocation,
278
+ ): Promise<{ cwd: string; projectDir: string; store: ObservationBaselineStoreConfig; snapshots?: unknown }> {
279
+ const cwd = resolve(opts?.cwd ?? process.cwd());
280
+ const { findProjectConfigPastFragments, loadChantConfigUpward } = await import("../config");
281
+ let config: ObservationBaselineLocation["config"] = opts?.config;
282
+ if (!opts?.store && config === undefined) {
283
+ try {
284
+ config = (await loadChantConfigUpward(cwd)).config;
285
+ } catch {
286
+ config = undefined;
287
+ }
288
+ }
289
+ // The `file` home is relative to the project, not to wherever the command
290
+ // ran: a run from a member's subdirectory writes under that member's own
291
+ // directory, as every other store writes under its member path (#2538).
292
+ const projectDir = findProjectConfigPastFragments(cwd).dir;
293
+ return { cwd, projectDir, store: opts?.store ?? observationBaselineStoreConfig(config?.observationBaseline), snapshots: config?.snapshots };
294
+ }
295
+
296
+ /** The `file` home's path for one environment, under the project directory. */
297
+ function baselineFile(projectDir: string, store: ObservationBaselineStoreConfig, env: string): string {
298
+ return join(projectDir, store.dir ?? OBSERVATION_BASELINE_DIR, `${env}.json`);
299
+ }
300
+
301
+ async function s3For(store: ObservationBaselineStoreConfig, snapshots: unknown) {
302
+ const { S3Medium } = await import("./snapshot-store-s3");
303
+ const { snapshotStoreConfig } = await import("./snapshot-store");
304
+ let settings = store.s3;
305
+ if (!settings) {
306
+ const snap = snapshotStoreConfig(snapshots);
307
+ if (snap.store !== "s3") throw new Error("the s3 observation baseline store needs observationBaseline.s3, or a snapshots store that is s3");
308
+ const { store: _s, ...rest } = snap;
309
+ settings = rest;
310
+ }
311
+ return new S3Medium(settings);
312
+ }
313
+
314
+ /** Where a baseline for `environment` lives in the configured home, for messages. */
315
+ export async function observationBaselineWhere(environment: string, opts?: ObservationBaselineLocation): Promise<string> {
316
+ const { cwd, store, snapshots } = await resolveLocation(opts);
317
+ switch (store.store) {
318
+ case "file":
319
+ return join(store.dir ?? OBSERVATION_BASELINE_DIR, `${encodeIndexSegment(environment)}.json`);
320
+ case "git-ref":
321
+ return `${store.ref ?? OBSERVATION_BASELINE_REF}:${await ledgerDir(encodeIndexSegment(environment), { cwd })}.json`;
322
+ case "s3":
323
+ return `${(await s3For(store, snapshots)).where}/observation-baseline/${await ledgerDir(encodeIndexSegment(environment), { cwd })}.json`;
324
+ case "none":
325
+ return "nowhere (the observation baseline store is none)";
326
+ }
327
+ }
223
328
 
224
329
  /**
225
- * Read the accepted baseline for an environment. Returns `null` when the branch,
226
- * the environment, or the file does not exist — every one of which means
227
- * "nothing accepted yet", the normal state before anyone runs
228
- * `--update-baseline`.
330
+ * Read the accepted baseline for an environment. Returns `null` when there is
331
+ * none, the normal state before anyone runs `--update-baseline`. When the
332
+ * configured home has none, a baseline written before #4519 on the
333
+ * `chant/lifecycle` branch is read instead.
229
334
  */
230
335
  export async function readObservationBaseline(
231
336
  environment: string,
232
- opts?: { cwd?: string },
337
+ opts?: ObservationBaselineLocation,
233
338
  ): Promise<ObservationBaseline | null> {
234
- return parseBaseline(await readBlobFromPath(environment, OBSERVATION_BASELINE_FILE, opts));
339
+ const { cwd, projectDir, store, snapshots } = await resolveLocation(opts);
340
+ let text: string | null = null;
341
+ switch (store.store) {
342
+ case "none":
343
+ return null;
344
+ case "file":
345
+ try {
346
+ text = readFileSync(baselineFile(projectDir, store, encodeIndexSegment(environment)), "utf8");
347
+ } catch {
348
+ text = null;
349
+ }
350
+ break;
351
+ case "git-ref": {
352
+ const { getRuntime } = await import("../runtime-adapter");
353
+ const path = `${await ledgerDir(encodeIndexSegment(environment), { cwd })}.json`;
354
+ const r = await getRuntime().spawn(["git", "cat-file", "blob", `${store.ref ?? OBSERVATION_BASELINE_REF}:${path}`], { cwd });
355
+ text = r.exitCode === 0 ? r.stdout : null;
356
+ break;
357
+ }
358
+ case "s3":
359
+ text = await (await s3For(store, snapshots)).get(`observation-baseline/${await ledgerDir(encodeIndexSegment(environment), { cwd })}.json`);
360
+ break;
361
+ }
362
+ return parseBaseline(text ?? (await readBlobFromPath(environment, OBSERVATION_BASELINE_FILE, { cwd })));
235
363
  }
236
364
 
237
- /** Write the accepted baseline to the orphan branch. Returns the new commit SHA. */
365
+ /** What {@link saveObservationBaseline} did. `file` means written in the working tree, for the caller to commit. */
366
+ export interface SavedObservationBaseline {
367
+ where: string;
368
+ published: "file" | "pushed" | "local" | "failed" | "none";
369
+ detail?: string;
370
+ }
371
+
372
+ /** Write the accepted baseline to its home, and push it when the home is a ref. */
373
+ export async function saveObservationBaseline(
374
+ baseline: ObservationBaseline,
375
+ opts?: ObservationBaselineLocation,
376
+ ): Promise<SavedObservationBaseline> {
377
+ const { cwd, projectDir, store, snapshots } = await resolveLocation(opts);
378
+ const where = await observationBaselineWhere(baseline.environment, { cwd, store, ...(snapshots !== undefined ? { config: { snapshots } } : {}) });
379
+ const content = serializeBaseline(baseline);
380
+ const env = encodeIndexSegment(baseline.environment);
381
+ switch (store.store) {
382
+ case "none":
383
+ return { where, published: "none" };
384
+ case "file": {
385
+ const file = baselineFile(projectDir, store, env);
386
+ mkdirSync(dirname(file), { recursive: true });
387
+ const tmp = `${file}.${process.pid}.tmp`;
388
+ writeFileSync(tmp, content);
389
+ renameSync(tmp, file);
390
+ return { where, published: "file" };
391
+ }
392
+ case "git-ref": {
393
+ const { commitFilesToRef, publishRef } = await import("./snapshot-store");
394
+ const ref = store.ref ?? OBSERVATION_BASELINE_REF;
395
+ const path = `${await ledgerDir(env, { cwd })}.json`;
396
+ await commitFilesToRef(ref, [{ path, content }], "Accepted observation baseline", { cwd });
397
+ const pushed = await publishRef(ref, new Map([[path, content]]), "Accepted observation baseline", { cwd });
398
+ return { where, published: pushed.status === "pushed" ? "pushed" : pushed.status === "local" ? "local" : "failed", ...(pushed.detail ? { detail: pushed.detail } : {}) };
399
+ }
400
+ case "s3":
401
+ await (await s3For(store, snapshots)).put(`observation-baseline/${await ledgerDir(env, { cwd })}.json`, content);
402
+ return { where, published: "pushed" };
403
+ }
404
+ }
405
+
406
+ /** Write the accepted baseline to its home. Returns where it went. */
238
407
  export async function writeObservationBaseline(
239
408
  baseline: ObservationBaseline,
240
- opts?: { cwd?: string },
409
+ opts?: ObservationBaselineLocation,
241
410
  ): Promise<string> {
242
- return writeBlobToPath(
243
- baseline.environment,
244
- OBSERVATION_BASELINE_FILE,
245
- serializeBaseline(baseline),
246
- "Accepted observation baseline",
247
- opts,
248
- );
411
+ return (await saveObservationBaseline(baseline, opts)).where;
249
412
  }
@@ -96,6 +96,8 @@ function qualifyObservation(obs: NormalizedObservation, stackName: string): Norm
96
96
  notes: obs.notes,
97
97
  // Exports are already keyed by stack (#1279); nothing to qualify.
98
98
  ...(obs.stackExports ? { stackExports: obs.stackExports } : {}),
99
+ // Edge endpoints are resource ids, so they re-key with them (#4528).
100
+ ...(obs.edges ? { edges: obs.edges.map((e) => ({ ...e, from: `${stackName}::${e.from}`, to: `${stackName}::${e.to}` })) } : {}),
99
101
  };
100
102
  }
101
103
 
@@ -518,13 +520,16 @@ function pushObservation(
518
520
  // Dependencies ride alongside the managed resources so they become nodes, and
519
521
  // carry `referencedBy` so every consumer can still tell the two apart.
520
522
  const hasDependencies = Object.keys(dependencies.resources).length > 0;
523
+ // The read's own edges (#4528, an engine's graph) and the dependency
524
+ // closure's ride together; `buildLiveGraphIr` dedupes them.
525
+ const edges = [...(observed.edges ?? []), ...dependencies.edges];
521
526
  observations.push({
522
527
  lexicon,
523
528
  resources: hasDependencies
524
529
  ? { ...observed.resources, ...dependencies.resources }
525
530
  : observed.resources,
526
531
  ...(unobservedNames.length > 0 ? { unobserved: observed.unobserved } : {}),
527
- ...(dependencies.edges.length > 0 ? { edges: dependencies.edges } : {}),
532
+ ...(edges.length > 0 ? { edges } : {}),
528
533
  ...(observed.stackExports && Object.keys(observed.stackExports).length > 0
529
534
  ? { stackExports: observed.stackExports }
530
535
  : {}),