@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,205 @@
1
+ /**
2
+ * `member-outputs` (#4455): a chant member's parameter planned against
3
+ * another workspace member's output, read through that member's engine.
4
+ *
5
+ * The estate is the three-cloud demo's: `dns` reads `azureEndpoint`, which
6
+ * the `azure` member publishes. `azure` is a choudoufu member, read through
7
+ * a fake engine source standing in for the terraform lexicon's
8
+ * `choudoufuOutputSource`; `records` is dns's deploy, a fake step that
9
+ * plans its parameters the way `cfn-deploy` does (a hole when an input is
10
+ * not known yet).
11
+ */
12
+
13
+ import { describe, expect, test } from "vitest";
14
+ import { CapabilityRegistry, type DeployContext } from "../capability";
15
+ import { registerCapabilityPlugins } from "../capability-plugin-loader";
16
+ import type { CapabilityPlugin } from "../capability-plugin";
17
+ import { starterCapabilityPlugin } from "../starter-plugin";
18
+ import { planFanOut } from "../fan-out";
19
+ import { applyPrSet, approvalOf, decidePrGate, planPrSet, readOnlyLedger, type PrPlanSet } from "../pr-run";
20
+ import { memoryGateLedgerPort } from "../../op/gate";
21
+ import { computePlanDigest } from "../../lifecycle/plan-digest";
22
+ import { deployUnits } from "../deploy-units";
23
+ import type { DriverComponent } from "../driver";
24
+ import {
25
+ EngineOutputSourceMissingError,
26
+ MEMBER_OUTPUTS_KIND,
27
+ memberOutputsCapability,
28
+ memberOutputsComponent,
29
+ memberOutputsMemberName,
30
+ MemberOutputsPendingError,
31
+ type EngineMemberRef,
32
+ type EngineOutputSource,
33
+ type EnginePlannedOutputs,
34
+ } from "./member-outputs";
35
+
36
+ interface RecordsInput {
37
+ inputs: Record<string, unknown>;
38
+ }
39
+
40
+ /** dns's two components: the azure member's outputs, and the records reading `azureEndpoint`. */
41
+ const ESTATE: DriverComponent[] = [
42
+ memberOutputsComponent("azure") as unknown as DriverComponent,
43
+ {
44
+ name: "records",
45
+ dependsOn: ["azure"],
46
+ deploy: [{ phase: "Apply", steps: [{ kind: "fake-records", inputs: { azureEndpoint: { stackOutput: { stack: "azure", name: "azureEndpoint" } } } }] }],
47
+ },
48
+ ];
49
+
50
+ const WORKSPACE = "/ws";
51
+ const lookup = async (_cwd: string, member: string): Promise<EngineMemberRef> => {
52
+ if (member !== "azure") throw new Error(`no member ${member}`);
53
+ return { member, kind: "choudoufu", dir: `${WORKSPACE}/azure`, workspaceRoot: WORKSPACE };
54
+ };
55
+
56
+ function world() {
57
+ const w = {
58
+ /** What the azure estate's outputs are now. */
59
+ applied: { azureEndpoint: "web.old.azurecontainerapps.io" } as Record<string, unknown>,
60
+ /** What azure's plan says. */
61
+ plan: { known: { azureEndpoint: "web.old.azurecontainerapps.io" }, unknown: [] as string[], planDigest: "sha256:azure-plan", changes: false } as EnginePlannedOutputs,
62
+ /** What each plan of records saw. */
63
+ recordsPlanned: [] as Array<{ inputs: Record<string, unknown>; unresolved?: string[] }>,
64
+ recordsRan: [] as Array<Record<string, unknown>>,
65
+ refs: [] as EngineMemberRef[],
66
+ };
67
+ const source: EngineOutputSource = {
68
+ kinds: ["choudoufu"],
69
+ async applied(_ctx, ref) {
70
+ w.refs.push(ref);
71
+ return { ...w.applied };
72
+ },
73
+ async planned(_ctx, ref) {
74
+ w.refs.push(ref);
75
+ return { ...w.plan, known: { ...w.plan.known }, unknown: [...w.plan.unknown] };
76
+ },
77
+ };
78
+ const plugin: CapabilityPlugin = {
79
+ name: "fake-dns",
80
+ version: "0.0.0",
81
+ capabilities: () =>
82
+ [
83
+ {
84
+ kind: "fake-records",
85
+ async plan(ctx: DeployContext, input: RecordsInput) {
86
+ w.recordsPlanned.push({ inputs: { ...input.inputs }, ...(ctx.unresolvedInputs ? { unresolved: [...ctx.unresolvedInputs] } : {}) });
87
+ const planDigest = computePlanDigest("fake-records", { inputs: input.inputs, unresolved: ctx.unresolvedInputs ?? [] });
88
+ const holes = ctx.unresolvedInputs?.length ? [{ address: "records", reason: `inputs from ${ctx.unresolvedInputs.join(", ")}; re-checked on apply` }] : [];
89
+ return {
90
+ member: "records",
91
+ planDigest,
92
+ artifact: { planDigest },
93
+ changeSet: { member: { member: "records", lexicon: "aws", planner: "chant", status: "planned", planDigest, holes }, entries: [] },
94
+ };
95
+ },
96
+ async run(_ctx: DeployContext, input: RecordsInput) {
97
+ w.recordsRan.push({ ...input.inputs });
98
+ return {};
99
+ },
100
+ },
101
+ ] as never,
102
+ engineOutputSources: () => [source],
103
+ };
104
+ // The registry the CLI builds: the starter set, whose member-outputs gets the plugins' sources bound.
105
+ const starter = { ...starterCapabilityPlugin, capabilities: () => starterCapabilityPlugin.capabilities().filter((c) => c.kind !== MEMBER_OUTPUTS_KIND).concat([memberOutputsCapability({ lookup }) as never]) };
106
+ const registry = registerCapabilityPlugins(new CapabilityRegistry(), [starter as CapabilityPlugin, plugin]);
107
+ return { registry, state: w };
108
+ }
109
+
110
+ const NOW = "2026-10-25T00:00:00Z";
111
+
112
+ describe("member-outputs", () => {
113
+ test("is in the starter set, and a registry built without a source for the member's kind refuses by name", async () => {
114
+ expect(starterCapabilityPlugin.capabilities().map((c) => c.kind)).toContain(MEMBER_OUTPUTS_KIND);
115
+ const bare = memberOutputsCapability({ lookup });
116
+ await expect(bare.outputs!({ env: "prod", component: "azure" }, { member: "azure" })).rejects.toBeInstanceOf(EngineOutputSourceMissingError);
117
+ await expect(bare.outputs!({ env: "prod", component: "azure" }, { member: "azure" })).rejects.toThrow(/member "azure" is kind choudoufu.*Add "terraform"/);
118
+ });
119
+
120
+ test("the component is a deploy unit named after the member", () => {
121
+ expect(deployUnits(memberOutputsComponent("azure").deploy)).toEqual([{ unit: "azure", lexicon: "workspace" }]);
122
+ });
123
+
124
+ test("an applied output feeds dns's parameter when the change does not touch the member", async () => {
125
+ const w = world();
126
+ const plan = planFanOut({ components: ESTATE, changed: ["records"] });
127
+ const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
128
+ expect(set.failed).toBe(false);
129
+ expect(w.state.recordsPlanned).toEqual([{ inputs: { azureEndpoint: "web.old.azurecontainerapps.io" } }]);
130
+ expect(set.deployedOutputs).toEqual({ azure: ["azureEndpoint"] });
131
+ expect(w.state.refs[0]).toEqual({ member: "azure", kind: "choudoufu", dir: "/ws/azure", workspaceRoot: "/ws" });
132
+ const records = set.members.find((m) => m.member === "records")!;
133
+ expect(records.unknownInputs).toBeUndefined();
134
+ expect(records.reads).toEqual([{ upstream: "azure", outputs: ["azureEndpoint"] }]);
135
+ });
136
+
137
+ test("a planned output the member's plan knows feeds dns's parameter when the change touches the member", async () => {
138
+ const w = world();
139
+ w.state.plan = { known: { azureEndpoint: "web.new.azurecontainerapps.io" }, unknown: [], planDigest: "sha256:azure-v2", changes: true };
140
+ const plan = planFanOut({ components: ESTATE, changed: ["azure"] });
141
+ const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
142
+ expect(w.state.recordsPlanned).toEqual([{ inputs: { azureEndpoint: "web.new.azurecontainerapps.io" } }]);
143
+ expect(set.plannedOutputs).toEqual({ azure: ["azureEndpoint"] });
144
+ const azure = set.doc.members.find((m) => m.member === memberOutputsMemberName("azure"))!;
145
+ expect(azure.holes).toEqual([]);
146
+ // The approval binds the member's plan: another plan of azure moves the set's digest.
147
+ const moved = world();
148
+ moved.state.plan = { ...w.state.plan, planDigest: "sha256:azure-v3" };
149
+ const again = await planPrSet({ components: ESTATE, plan, registry: moved.registry, env: "prod" });
150
+ expect(again.doc.digest).not.toBe(set.doc.digest);
151
+ });
152
+
153
+ test("an unknown upstream output holds dns back, naming the member and the output", async () => {
154
+ const w = world();
155
+ w.state.plan = { known: {}, unknown: ["azureEndpoint"], planDigest: "sha256:azure-new-app", changes: true };
156
+ const plan = planFanOut({ components: ESTATE, changed: ["azure"] });
157
+ const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
158
+ expect(set.failed).toBe(false);
159
+
160
+ // records was planned without the value, told whose output it waits on.
161
+ expect(w.state.recordsPlanned).toEqual([{ inputs: {}, unresolved: ["azure"] }]);
162
+ const records = set.members.find((m) => m.member === "records")!;
163
+ expect(records.unknownInputs).toEqual(["azure.azureEndpoint"]);
164
+ expect(records.reads).toEqual([{ upstream: "azure", outputs: ["azureEndpoint"], unknown: ["azureEndpoint"] }]);
165
+ expect(set.doc.members.find((m) => m.member === "records")!.holes).toEqual([{ address: "records", reason: "inputs from azure; re-checked on apply" }]);
166
+ // The member's own part names the output.
167
+ expect(set.doc.members.find((m) => m.member === memberOutputsMemberName("azure"))!.holes).toEqual([
168
+ { address: "azure.azureEndpoint", type: "output", reason: "azure.azureEndpoint is known only after azure applies; what reads it is planned again then" },
169
+ ]);
170
+
171
+ // On merge, before azure's own pipeline applied: the read refuses, so records is blocked behind it and nothing reads a stale value.
172
+ const early = await applyPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", set });
173
+ expect(early.members.find((m) => m.member === "records")).toMatchObject({ status: "blocked", blockedBy: "azure" });
174
+ expect(w.state.recordsRan).toEqual([]);
175
+
176
+ // Once azure applied: records is held back with the reason, and planned again against the value azure applied as.
177
+ w.state.applied = { azureEndpoint: "web.new.azurecontainerapps.io" };
178
+ w.state.plan = { known: { azureEndpoint: "web.new.azurecontainerapps.io" }, unknown: [], planDigest: "sha256:azure-applied", changes: false };
179
+ const decideNothing = async ({ gate, set: round }: { gate: string; set: PrPlanSet }) => {
180
+ const check = await decidePrGate(readOnlyLedger(memoryGateLedgerPort()), { op: "pr-12", gate, digest: round.doc.digest, now: NOW });
181
+ return { satisfied: check.satisfied, approval: approvalOf(check) };
182
+ };
183
+ const held = await applyPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", set, decideRound: decideNothing });
184
+ expect(held.status).toBe("waiting");
185
+ expect(held.members.find((m) => m.member === "records")).toMatchObject({
186
+ status: "waiting",
187
+ round: 2,
188
+ held: "its plan read azure.azureEndpoint before that output applied as another value",
189
+ });
190
+ expect(w.state.recordsPlanned.at(-1)).toEqual({ inputs: { azureEndpoint: "web.new.azurecontainerapps.io" } });
191
+ expect(w.state.recordsRan).toEqual([]);
192
+ });
193
+
194
+ test("run reads and never applies: a member with changes left refuses, one without answers its applied outputs", async () => {
195
+ const w = world();
196
+ const capability = w.registry.resolve(MEMBER_OUTPUTS_KIND);
197
+ w.state.plan = { ...w.state.plan, changes: true };
198
+ await expect(capability.run({ env: "prod", component: "azure" }, { member: "azure" } as never)).rejects.toBeInstanceOf(MemberOutputsPendingError);
199
+ w.state.plan = { ...w.state.plan, changes: false };
200
+ await expect(capability.run({ env: "prod", component: "azure" }, { member: "azure" } as never)).resolves.toEqual({
201
+ member: "azure",
202
+ outputs: { azureEndpoint: "web.old.azurecontainerapps.io" },
203
+ });
204
+ });
205
+ });
@@ -0,0 +1,308 @@
1
+ /**
2
+ * `member-outputs`: another workspace member's outputs, read through the
3
+ * engine that member runs on, so a chant member's parameters can be planned
4
+ * against them (#4455, design record choudoufu#2258 lane T6).
5
+ *
6
+ * In `behold/example-three-cloud`, `dns` (a chant member) reads each cloud's
7
+ * endpoint as a parameter of the same name as the output the cloud's member
8
+ * publishes. When the Azure site is a choudoufu estate, `azureEndpoint` is a
9
+ * Terraform root output, and when the AWS site is a Pulumi stack it is a
10
+ * stack output. Neither is a chant component, so there is nothing for a
11
+ * `stackOutput()` reference to read. This verb is that something: a
12
+ * component in the consumer's project whose one step names the upstream
13
+ * member, and whose capability answers the two questions the components
14
+ * machinery already asks of an upstream:
15
+ *
16
+ * - `outputs` (applied): what the member exposes now. A pull request that
17
+ * does not touch the member plans the consumer against these
18
+ * (`deployedOutputs`, #4104).
19
+ * - `plan` (planned): what the member will expose once its own plan applies,
20
+ * with the outputs known only after apply named (`PlannedOutputs`, #3999).
21
+ * A pull request that touches the member plans the consumer against the
22
+ * known ones. A reference to an unknown one is unresolved, so the
23
+ * consumer's step is planned with `DeployContext.unresolvedInputs` naming
24
+ * the member, the pull request's report lists `<member>.<output>` under the
25
+ * consumer's `unknownInputs`, and the apply holds the consumer back until
26
+ * the member applied, then plans it again (`pr-run.ts`, "Planning again
27
+ * once an upstream applied"). That is the hold chant already puts on a
28
+ * root waiting on another root's unknown output; nothing here adds a
29
+ * second one.
30
+ *
31
+ * The consumer writes it as two components:
32
+ *
33
+ * ```ts
34
+ * export const azure = memberOutputsComponent("azure");
35
+ * export const records: Component = {
36
+ * name: "records",
37
+ * dependsOn: ["azure"],
38
+ * deploy: [phase("Apply", [{ kind: "cfn-deploy", template: "dist/dns.json",
39
+ * inputs: { azureEndpoint: stackOutput("azure", "azureEndpoint") } }])],
40
+ * };
41
+ * ```
42
+ *
43
+ * ## Engines: the seam
44
+ *
45
+ * The member is looked up in the workspace declaration (`chant.workspace.json`)
46
+ * by name, and its `kind` picks the {@link EngineOutputSource} that reads it.
47
+ * Sources are not capabilities: each lexicon that drives an engine
48
+ * contributes one through `CapabilityPlugin.engineOutputSources()`, the way
49
+ * the cloud lexicons contribute registry logins to `publish-image`, and the
50
+ * registry binds them into this one verb when it is built
51
+ * (`registerCapabilityPlugins`). So two engines never register the same
52
+ * kind, and a consumer's step does not change when the member moves between
53
+ * engines.
54
+ *
55
+ * - `choudoufu` (the terraform lexicon, `choudoufuOutputSource`): applied
56
+ * values from `choudoufu output -json` in the member's directory, which in
57
+ * live mode reads the estate's record store; planned values from
58
+ * `live-plan-set -json` over the member's root, its plan's
59
+ * `planned_values.outputs` with every output whose `output_changes` entry
60
+ * marks any part `after_unknown` named unknown (`terraformPlannedOutputs`).
61
+ * - `pulumi` (the pulumi lexicon, `pulumiOutputSource`): applied values
62
+ * from `pulumi stack output --json --show-secrets=false`, or from the
63
+ * member's checked-in `stack-export.json` when chant must not run pulumi
64
+ * (`CHANT_PULUMI_APPLIED_OUTPUTS=export`); planned values from `pulumi
65
+ * preview --json`, the stack step's outputs, where an output holding
66
+ * Pulumi's unknown sentinel is unknown. Secret outputs are left out of
67
+ * both. A member on plunge runs the same commands under `plunge`.
68
+ * Nothing in this module or in the consumer changes for it.
69
+ *
70
+ * ## Run reads, it never applies
71
+ *
72
+ * The member applies through its own pipeline. `run` checks that the
73
+ * member's plan has nothing left to apply (`changes` false) and answers its
74
+ * applied outputs; a member with changes still to apply refuses the run
75
+ * with {@link MemberOutputsPendingError}, so the consumer is blocked behind
76
+ * it and never applies against values from before the member's change. A
77
+ * workspace orders the member's own run before the consumer's (members that
78
+ * link to a producer run after it, `chant workspace run`).
79
+ *
80
+ * ## Which changes reach the member
81
+ *
82
+ * The component is a deploy unit named after the member (`DEPLOY_UNIT_RULES`),
83
+ * so a change signal that names the member as a changed unit puts it in the
84
+ * change. The terraform lexicon names it when the consumer's
85
+ * `terraform.roots` has an entry of that name whose files changed;
86
+ * `--from-affected` can name it too.
87
+ */
88
+
89
+ import type { Capability, CapabilityPlan, DeployContext, PlannedOutputs } from "../capability";
90
+ import type { ChangeSetHole, ChangeSetPart } from "../../change-set";
91
+ import { computePlanDigest } from "../../lifecycle/plan-digest";
92
+ import type { Component } from "../component";
93
+
94
+ /** The step kind. */
95
+ export const MEMBER_OUTPUTS_KIND = "member-outputs";
96
+
97
+ /** The `member-outputs` step's input. */
98
+ export interface MemberOutputsInput {
99
+ /** The upstream member's name in the workspace declaration. Also the component's name, by convention. */
100
+ member: string;
101
+ /**
102
+ * Where the declaration search starts. Default: the current directory,
103
+ * which is the consumer's project root under `chant run`.
104
+ */
105
+ cwd?: string;
106
+ }
107
+
108
+ /** What `run` returns: the member's applied outputs, under `outputs`, where the driver collects a stack's outputs from. */
109
+ export interface MemberOutputsOutput {
110
+ member: string;
111
+ outputs: Record<string, unknown>;
112
+ }
113
+
114
+ /** The upstream member a source reads, as the workspace declares it. */
115
+ export interface EngineMemberRef {
116
+ /** The member's name. */
117
+ member: string;
118
+ /** Its declared kind, which picked the source: `choudoufu`, `pulumi`. */
119
+ kind: string;
120
+ /** Its directory, absolute. */
121
+ dir: string;
122
+ /** The workspace root, absolute. A source that must run from above the member's directory runs from here. */
123
+ workspaceRoot: string;
124
+ }
125
+
126
+ /** What a source's plan of the member says about its outputs. */
127
+ export interface EnginePlannedOutputs extends PlannedOutputs {
128
+ /**
129
+ * A digest of the member's plan, the engine's own or chant's over it. The
130
+ * consumer's approval binds it, so a plan of the member that moves after
131
+ * review moves the consumer's digest too.
132
+ */
133
+ planDigest: string;
134
+ /** Whether the member's plan has anything to apply. `run` refuses while it does. */
135
+ changes: boolean;
136
+ }
137
+
138
+ /**
139
+ * Reads one engine's member outputs (#4455). One per engine, contributed by
140
+ * the lexicon that drives it through `CapabilityPlugin.engineOutputSources()`.
141
+ */
142
+ export interface EngineOutputSource {
143
+ /** The member kinds this source reads, as declared in `chant.workspace.json`. */
144
+ readonly kinds: readonly string[];
145
+ /** The member's outputs as applied now, by name. */
146
+ applied(ctx: DeployContext, ref: EngineMemberRef): Promise<Record<string, unknown>>;
147
+ /** The member's outputs once its current plan applies, the ones known only after apply named. */
148
+ planned(ctx: DeployContext, ref: EngineMemberRef): Promise<EnginePlannedOutputs>;
149
+ }
150
+
151
+ /** Finds a member in the workspace above `cwd`: its kind and directory. */
152
+ export type MemberLookup = (cwd: string, member: string) => Promise<EngineMemberRef>;
153
+
154
+ /** No source for the member's kind is loaded. */
155
+ export class EngineOutputSourceMissingError extends Error {
156
+ constructor(
157
+ readonly member: string,
158
+ readonly kind: string,
159
+ readonly available: readonly string[],
160
+ ) {
161
+ super(
162
+ `${MEMBER_OUTPUTS_KIND}: member "${member}" is kind ${kind}, and no loaded lexicon reads that engine's outputs` +
163
+ (available.length > 0 ? ` (loaded: ${available.join(", ")})` : "") +
164
+ `. ${kind === "choudoufu" ? 'Add "terraform" to the project\'s lexicons.' : kind === "pulumi" ? 'Add "pulumi" to the project\'s lexicons.' : "Load the lexicon that drives it."}`,
165
+ );
166
+ this.name = "EngineOutputSourceMissingError";
167
+ }
168
+ }
169
+
170
+ /** The member's plan has changes its own pipeline has not applied. Nothing was read. */
171
+ export class MemberOutputsPendingError extends Error {
172
+ constructor(readonly member: string) {
173
+ super(
174
+ `${MEMBER_OUTPUTS_KIND}: member "${member}" has changes its own pipeline has not applied yet, ` +
175
+ `so its outputs are still the ones from before them. Apply ${member} first; whatever reads its outputs runs after it.`,
176
+ );
177
+ this.name = "MemberOutputsPendingError";
178
+ }
179
+ }
180
+
181
+ /** The member a {@link MemberOutputsInput} reads plans as, in the consumer's change set. */
182
+ export const memberOutputsMemberName = (member: string): string => `${member}#outputs`;
183
+
184
+ /**
185
+ * The member's name in the declaration above `cwd`, with its kind and
186
+ * directory. Reads the working tree. Imported lazily: the verb set is
187
+ * loaded where the workspace reader's dependencies may not be.
188
+ */
189
+ export const workspaceMemberLookup: MemberLookup = async (cwd, member) => {
190
+ const { locateWorkspace } = await import("../../workspace/which-chant");
191
+ const { readDeclaration } = await import("../../workspace/declaration");
192
+ const { join } = await import("node:path");
193
+ const located = locateWorkspace(cwd);
194
+ const declaration = readDeclaration(located.tree);
195
+ const found = declaration.members.find((m) => m.name === member);
196
+ if (!found) {
197
+ const names = declaration.members.map((m) => m.name).sort();
198
+ throw new Error(`${MEMBER_OUTPUTS_KIND}: the workspace at ${located.rootOnDisk} declares no member "${member}" (members: ${names.join(", ")})`);
199
+ }
200
+ return {
201
+ member: found.name,
202
+ kind: found.kind,
203
+ dir: found.dir === "." ? located.rootOnDisk : join(located.rootOnDisk, ...found.dir.split("/")),
204
+ workspaceRoot: located.rootOnDisk,
205
+ };
206
+ };
207
+
208
+ function requireMember(input: MemberOutputsInput): void {
209
+ if (typeof input?.member !== "string" || input.member === "") {
210
+ throw new Error(`${MEMBER_OUTPUTS_KIND}: "member" is required and names a member of the workspace`);
211
+ }
212
+ }
213
+
214
+ /** Implemented by a capability that wants the plugins' engine output sources injected when a registry is built. */
215
+ export interface EngineOutputSourceConsumer {
216
+ withEngineOutputSources(sources: readonly EngineOutputSource[]): Capability<never, unknown>;
217
+ }
218
+
219
+ export function isEngineOutputSourceConsumer(value: unknown): value is EngineOutputSourceConsumer {
220
+ return typeof value === "object" && value !== null && typeof (value as EngineOutputSourceConsumer).withEngineOutputSources === "function";
221
+ }
222
+
223
+ /**
224
+ * The member's part of the consumer's change set: no entries, since the
225
+ * member's own pipeline plans and applies its changes, and a hole per output
226
+ * known only after apply, naming the member and the output.
227
+ */
228
+ function outputsPart(member: string, planDigest: string, unknown: readonly string[]): ChangeSetPart {
229
+ const holes: ChangeSetHole[] = unknown.map((name) => ({
230
+ address: `${member}.${name}`,
231
+ type: "output",
232
+ reason: `${member}.${name} is known only after ${member} applies; what reads it is planned again then`,
233
+ }));
234
+ return {
235
+ member: { member: memberOutputsMemberName(member), lexicon: "chant", planner: "chant", status: "planned", planDigest, holes },
236
+ entries: [],
237
+ };
238
+ }
239
+
240
+ export interface MemberOutputsCapabilityOptions {
241
+ /** The engines' sources. Bound by the registry from the loaded plugins; empty until then. */
242
+ sources?: readonly EngineOutputSource[];
243
+ /** How a member is found. Default: {@link workspaceMemberLookup}. */
244
+ lookup?: MemberLookup;
245
+ }
246
+
247
+ /** `member-outputs` over the given sources. */
248
+ export function memberOutputsCapability(
249
+ options: MemberOutputsCapabilityOptions = {},
250
+ ): Capability<MemberOutputsInput, MemberOutputsOutput> & EngineOutputSourceConsumer {
251
+ const sources = options.sources ?? [];
252
+ const lookup = options.lookup ?? workspaceMemberLookup;
253
+
254
+ async function resolve(input: MemberOutputsInput): Promise<{ ref: EngineMemberRef; source: EngineOutputSource }> {
255
+ requireMember(input);
256
+ const ref = await lookup(input.cwd ?? process.cwd(), input.member);
257
+ const source = sources.find((s) => s.kinds.includes(ref.kind));
258
+ if (!source) throw new EngineOutputSourceMissingError(ref.member, ref.kind, [...new Set(sources.flatMap((s) => s.kinds))].sort());
259
+ return { ref, source };
260
+ }
261
+
262
+ return {
263
+ kind: MEMBER_OUTPUTS_KIND,
264
+ // It reads; the member's own pipeline applies. Nothing to compensate.
265
+ rollbackPolicy: "none-by-design",
266
+ targets: { refuse: "it reads another member's outputs; narrow that member's own plan instead" },
267
+ async plan(ctx: DeployContext, input: MemberOutputsInput): Promise<CapabilityPlan> {
268
+ const { ref, source } = await resolve(input);
269
+ const planned = await source.planned(ctx, ref);
270
+ const unknown = [...new Set(planned.unknown)].sort();
271
+ const known = Object.fromEntries(Object.entries(planned.known).filter(([name]) => !unknown.includes(name)));
272
+ const member = memberOutputsMemberName(ref.member);
273
+ const planDigest = computePlanDigest(MEMBER_OUTPUTS_KIND, { env: ctx.env, member: ref.member, kind: ref.kind, plan: planned.planDigest, known, unknown });
274
+ return { member, planDigest, changeSet: outputsPart(ref.member, planDigest, unknown), outputs: { known, unknown } };
275
+ },
276
+ async outputs(ctx: DeployContext, input: MemberOutputsInput): Promise<Record<string, unknown>> {
277
+ const { ref, source } = await resolve(input);
278
+ return source.applied(ctx, ref);
279
+ },
280
+ async run(ctx: DeployContext, input: MemberOutputsInput): Promise<MemberOutputsOutput> {
281
+ const { ref, source } = await resolve(input);
282
+ const now = await source.planned(ctx, ref);
283
+ if (now.changes) throw new MemberOutputsPendingError(ref.member);
284
+ return { member: ref.member, outputs: await source.applied(ctx, ref) };
285
+ },
286
+ withEngineOutputSources(next: readonly EngineOutputSource[]) {
287
+ return memberOutputsCapability({ ...options, sources: next }) as unknown as Capability<never, unknown>;
288
+ },
289
+ };
290
+ }
291
+
292
+ /** The starter set's `member-outputs`, with no sources until a registry binds the loaded plugins' ones. */
293
+ export const memberOutputsStarterCapability = memberOutputsCapability();
294
+
295
+ /**
296
+ * A component in the consumer's project that stands for workspace member
297
+ * `member` (#4455): one step reading its outputs. Named after the member, so
298
+ * `stackOutput(member, "<output>")` reads them and `dependsOn: [member]`
299
+ * orders the consumer after it.
300
+ */
301
+ export function memberOutputsComponent(member: string, options: { cwd?: string } = {}): Component {
302
+ return {
303
+ name: member,
304
+ archetype: "infra",
305
+ dependsOn: [],
306
+ deploy: [{ phase: "Outputs", steps: [{ kind: MEMBER_OUTPUTS_KIND, member, ...(options.cwd ? { cwd: options.cwd } : {}) }] }],
307
+ };
308
+ }
@@ -27,6 +27,7 @@
27
27
  * `@<component>.publish.*`) resolves.
28
28
  */
29
29
 
30
+ import type { PlanShape } from "./covered-round";
30
31
  import { assertTargetsAccepted, resolveStepInput, unresolvedWiringSources, type DriverComponent, type DriverPhase, type DriverStep } from "./driver";
31
32
  import { componentPlanDigest } from "./gate-plan";
32
33
  import { componentDeployInputs, type ComponentDeployInputs } from "./deploy-inputs";
@@ -44,7 +45,7 @@ import type { ChangeSetPart } from "../change-set";
44
45
  * merges a run's outputs; absent when no step's plan says.
45
46
  */
46
47
  export type WaveComponentPlan =
47
- | { component: string; members: WaveMember[]; plans: Record<string, unknown>; parts: ChangeSetPart[]; outputs?: PlannedOutputs }
48
+ | { component: string; members: WaveMember[]; plans: Record<string, unknown>; parts: ChangeSetPart[]; outputs?: PlannedOutputs; shapes?: Record<string, PlanShape> }
48
49
  | { component: string; error: string };
49
50
 
50
51
  /** A part with no entries, for a member whose planner describes no changes. `unbound` steps become its holes (#4155). */
@@ -101,6 +102,7 @@ export async function planWaveComponent(
101
102
  const plans: Record<string, unknown> = {};
102
103
  const parts: ChangeSetPart[] = [];
103
104
  let outputs: PlannedOutputs | undefined;
105
+ const shapes: Record<string, PlanShape> = {};
104
106
  assertTargetsAccepted(component, registry, ctx);
105
107
  for (const step of capabilitySteps(component.deploy)) {
106
108
  const capability = registry.resolve(step.kind);
@@ -112,6 +114,7 @@ export async function planWaveComponent(
112
114
  members.push(member);
113
115
  parts.push(planned.changeSet ?? emptyPart(member));
114
116
  if (planned.artifact !== undefined) plans[planned.member] = planned.artifact;
117
+ if (planned.shape) shapes[planned.member] = { ...planned.shape, member: planned.member };
115
118
  if (planned.outputs) outputs = mergePlannedOutputs(outputs, planned.outputs);
116
119
  }
117
120
  // #4155: the steps without a plan, which no member above covers.
@@ -141,7 +144,7 @@ export async function planWaveComponent(
141
144
  parts.push(emptyPart(member, read.unbound));
142
145
  }
143
146
  }
144
- return { component: component.name, members, plans, parts, ...(outputs ? { outputs } : {}) };
147
+ return { component: component.name, members, plans, parts, ...(outputs ? { outputs } : {}), ...(Object.keys(shapes).length > 0 ? { shapes } : {}) };
145
148
  } catch (err) {
146
149
  return { component: component.name, error: err instanceof Error ? err.message : String(err) };
147
150
  }
package/src/config.ts CHANGED
@@ -34,6 +34,20 @@ export {
34
34
  } from "./config-environments";
35
35
  import { EXECUTOR_FORGES, isOwnershipParamRef, resolveOwnershipStack, type EnvironmentDeclaration, type OwnershipEnv } from "./config-environments";
36
36
 
37
+ /** An s3 index store's settings (#4519, ./lifecycle/snapshot-store-s3.ts). */
38
+ const S3StoreSettingsShape = {
39
+ bucket: z.string().min(1),
40
+ prefix: z.string().optional(),
41
+ region: z.string().min(1).optional(),
42
+ endpoint: z.string().url().optional(),
43
+ forcePathStyle: z.boolean().optional(),
44
+ credentials: z.object({
45
+ accessKeyId: z.string().min(1),
46
+ secretAccessKey: z.string().min(1),
47
+ sessionToken: z.string().min(1).optional(),
48
+ }).strict().optional(),
49
+ };
50
+
37
51
  /**
38
52
  * Zod schema for ChantConfig validation.
39
53
  */
@@ -139,6 +153,20 @@ export const ChantConfigSchema = z.object({
139
153
  exclude: z.array(z.string().min(1)).optional(),
140
154
  include: z.array(z.string().min(1)).optional(),
141
155
  rootOnly: z.boolean().optional(),
156
+ // Strict: a misspelt key would send snapshots somewhere nobody looks (#4519).
157
+ snapshots: z.discriminatedUnion("store", [
158
+ z.object({ store: z.literal("git-ref"), ref: z.string().regex(/^refs\/chant\//).optional(), readOrphanBranch: z.boolean().optional() }).strict(),
159
+ z.object({ store: z.literal("orphan-branch") }).strict(),
160
+ z.object({ store: z.literal("local"), dir: z.string().min(1).optional() }).strict(),
161
+ z.object({ store: z.literal("s3"), ...S3StoreSettingsShape }).strict(),
162
+ z.object({ store: z.literal("none") }).strict(),
163
+ ]).optional(),
164
+ observationBaseline: z.object({
165
+ store: z.enum(["file", "git-ref", "s3", "none"]),
166
+ dir: z.string().min(1).optional(),
167
+ ref: z.string().regex(/^refs\/chant\//).optional(),
168
+ s3: z.object(S3StoreSettingsShape).strict().optional(),
169
+ }).strict().optional(),
142
170
  }).passthrough();
143
171
 
144
172
  /**
@@ -504,6 +532,22 @@ export interface ChantConfig {
504
532
  * in a project with no `chant.workspace.json`.
505
533
  */
506
534
  rootOnly?: boolean;
535
+
536
+ /**
537
+ * Where snapshots and every other index are kept (#4519): `git-ref`
538
+ * (default, `refs/chant/snapshots`, still reading `chant/lifecycle` for
539
+ * snapshots written before), `local` (a directory), `s3` (a bucket under a
540
+ * prefix), `none`, or `orphan-branch` (read-only). `CHANT_SNAPSHOT_STORE`
541
+ * and its companions override it. See `./lifecycle/snapshot-store.ts`.
542
+ */
543
+ snapshots?: import("./lifecycle/snapshot-store").SnapshotStoreConfig;
544
+
545
+ /**
546
+ * Where accepted observation deviations are kept (#4519): `file` (default,
547
+ * `.chant/observation-baseline/<env>.json`, committed and reviewed),
548
+ * `git-ref`, `s3` or `none`. See `./lifecycle/observation-baseline.ts`.
549
+ */
550
+ observationBaseline?: import("./lifecycle/observation-baseline").ObservationBaselineStoreConfig;
507
551
  }
508
552
 
509
553
  /**
@@ -45,6 +45,7 @@
45
45
 
46
46
  import type { UnobservedEntity } from "./observation";
47
47
  import { isHeldElsewhere, isNormalizedHeldElsewhere, normalizeHeldElsewhere } from "./held-elsewhere";
48
+ import { isSensitiveKey } from "./sensitive-key";
48
49
 
49
50
  /**
50
51
  * A live property tree for one declared entity, already normalized by the
@@ -203,29 +204,10 @@ export const UNRESOLVED = "<chant:unresolved>";
203
204
  /** Value a masked (secret-bearing) property takes. Matches the thin path's convention. */
204
205
  export const MASKED = "[REDACTED]";
205
206
 
206
- /**
207
- * Property-name patterns that mark a value secret-bearing. Deliberately narrow
208
- * and key-name based: broadening this to e.g. `/key/i` would mask an AWS tag's
209
- * `Key` field and every `*KeyName` reference, which is how masking turns into
210
- * its own drift signal.
211
- *
212
- * Shared with the thin snapshot path (`./lifecycle/snapshot.ts`), which warns
213
- * on the same names — one list, so the two paths cannot disagree about what
214
- * counts as a secret.
215
- */
216
- export const SENSITIVE_KEY_PATTERNS: readonly RegExp[] = [
217
- /password/i,
218
- /secret/i,
219
- /token/i,
220
- /private.?key/i,
221
- /credential/i,
222
- /connection.?string/i,
223
- ];
224
-
225
- /** True when a property name looks secret-bearing (see {@link SENSITIVE_KEY_PATTERNS}). */
226
- export function isSensitiveKey(key: string): boolean {
227
- return SENSITIVE_KEY_PATTERNS.some((p) => p.test(key));
228
- }
207
+ // The secret-key list lives in `./sensitive-key.ts` so the drift record
208
+ // (`./drift-record.ts`) can redact with it inside the change-set bundle,
209
+ // which may not import this module's normalization code.
210
+ export { SENSITIVE_KEY_PATTERNS, isSensitiveKey } from "./sensitive-key";
229
211
 
230
212
  /** Which tree a hook is being invoked on. */
231
213
  export type DeepSide = "declared" | "live";