@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
@@ -15,6 +15,19 @@ vi.mock("./git", () => ({
15
15
  pushLifecycle: () => pushLifecycleMock(),
16
16
  }));
17
17
 
18
+ // Snapshots are written through the index store (#4519); this store hands each write to the mocks above.
19
+ vi.mock("./snapshot-store", async () => {
20
+ const { fakeSnapshotStore } = await import("./snapshot-store-fake");
21
+ const store = fakeSnapshotStore({
22
+ write: async (environment, key, json) => ({ written: true, version: (await writeSnapshotMock(environment, key, json)) as string }),
23
+ publish: async () => {
24
+ await pushLifecycleMock();
25
+ return { status: "pushed" };
26
+ },
27
+ });
28
+ return { resolveSnapshotStore: async () => store };
29
+ });
30
+
18
31
  const { takeSnapshot } = await import("./snapshot");
19
32
 
20
33
  function makeBuildResult(entitiesByLexicon: Record<string, string[]>): BuildResult {
@@ -10,7 +10,8 @@ import { observeDeep } from "./deep-observe";
10
10
  import type { DeepResourceObservation } from "../deep-observation";
11
11
  import { computeBuildDigest } from "./digest";
12
12
  import { collectDependencies, collectAmbient } from "./observe";
13
- import { writeSnapshot, snapshotStorageKey, getHeadCommit, pushLifecycle } from "./git";
13
+ import { snapshotStorageKey, getHeadCommit } from "./git";
14
+ import { resolveSnapshotStore, type SnapshotStore, type StorePublish } from "./snapshot-store";
14
15
  import { sortedJsonReplacer } from "../utils";
15
16
  import { formatUnobserved, normalizeObservation, unobservedAll, type UnobservedEntity } from "../observation";
16
17
  // One list of secret-bearing property names for both observation depths
@@ -72,9 +73,12 @@ function validateResources(
72
73
 
73
74
  export interface TakeSnapshotResult {
74
75
  snapshots: LifecycleSnapshot[];
76
+ /** The version the last write made: a commit on a git store, an object version on s3. Empty when nothing was written. */
75
77
  commit: string;
76
78
  warnings: string[];
77
79
  errors: string[];
80
+ /** Where the snapshots went (`refs/chant/snapshots`, a directory, `s3://...`), and whether they were kept (#4519). */
81
+ store?: { kind: SnapshotStore["kind"]; where: string; written: boolean; publish?: StorePublish };
78
82
  }
79
83
 
80
84
  /**
@@ -93,6 +97,8 @@ export async function takeSnapshot(
93
97
  /** Kinds the PROJECT manages, not just this stack — a region whose stack
94
98
  * declares no security group still has a default one (#1278). */
95
99
  ambientKinds?: string[];
100
+ /** The index store to write to (#4519). Default: the project's, from `snapshots` in chant.config.ts. */
101
+ store?: SnapshotStore;
96
102
  },
97
103
  ): Promise<TakeSnapshotResult> {
98
104
  const stack = opts?.stack;
@@ -285,21 +291,31 @@ export async function takeSnapshot(
285
291
  }
286
292
  }
287
293
 
288
- // Write all successful snapshots to git
294
+ // Write every successful snapshot through the index store (#4519). Masking
295
+ // already happened above, so the store never sees a secret.
296
+ const store = opts?.store ?? (await resolveSnapshotStore({ cwd: opts?.cwd }));
289
297
  let commitSha = "";
298
+ let written = false;
290
299
  for (const snapshot of snapshots) {
291
300
  const json = JSON.stringify(snapshot, sortedJsonReplacer, 2);
292
- commitSha = await writeSnapshot(
301
+ const result = await store.write(
293
302
  snapshot.environment,
294
303
  snapshotStorageKey(snapshot.lexicon, snapshot.stack),
295
304
  json,
296
- opts,
305
+ opts?.cwd ? { cwd: opts.cwd } : undefined,
297
306
  );
307
+ if (result.written) {
308
+ written = true;
309
+ commitSha = result.version ?? commitSha;
310
+ } else if (result.reason) {
311
+ warnings.push(`${snapshot.lexicon}: snapshot not kept: ${result.reason}`);
312
+ }
298
313
  }
299
314
 
300
315
  // Push to remote
301
- if (snapshots.length > 0) {
302
- await pushLifecycle(opts);
316
+ const publish = written ? await store.publish() : undefined;
317
+ if (publish && (publish.status === "stale" || publish.status === "failed")) {
318
+ warnings.push(`snapshot kept locally in ${store.where}, not pushed: ${publish.detail ?? publish.status}`);
303
319
  }
304
320
 
305
321
  return {
@@ -307,5 +323,6 @@ export async function takeSnapshot(
307
323
  commit: commitSha,
308
324
  warnings,
309
325
  errors,
326
+ store: { kind: store.kind, where: store.where, written, ...(publish ? { publish } : {}) },
310
327
  };
311
328
  }
@@ -0,0 +1,166 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { MASKED } from "../deep-observation";
3
+ import { indexCoverageFor, validateIndexDocument } from "./index-document";
4
+ import { listedNodeId, typeListing, type ListingLexicon, type ListingResult } from "./type-listing";
5
+
6
+ // #4521: a listing (behold#1029's shape) typed into an index version (behold#1025).
7
+ const lexicons: ListingLexicon[] = [
8
+ {
9
+ name: "aws",
10
+ listedTypes: { substrate: "aws", kindFor: (t) => (t === "AWS::S3::Bucket" || t === "AWS::RDS::DBInstance" ? t : undefined) },
11
+ deepNormalizationHooks: {
12
+ // Stands in for a lexicon noise rule: an ARN is server-populated.
13
+ prune: (node) => node.side === "live" && node.key === "Arn",
14
+ },
15
+ },
16
+ {
17
+ name: "k8s",
18
+ listedTypes: {
19
+ substrate: "kubernetes",
20
+ kindFor: (t) => (t === "apps/v1/Deployment" ? "K8s::Apps::Deployment" : undefined),
21
+ observedTree: ({ apiVersion: _a, kind: _k, ...rest }) => rest,
22
+ },
23
+ },
24
+ ];
25
+
26
+ function listing(overrides: Partial<ListingResult> = {}): ListingResult {
27
+ return {
28
+ listing: "v1",
29
+ source: { name: "behold-lister", version: "0.0.0-test" },
30
+ scope: { substrate: "aws", scopeId: "123456789012", locations: ["us-east-1"], types: "*" },
31
+ startedAt: "2026-10-26T09:14:03.120Z",
32
+ completedAt: "2026-10-26T09:14:09.000Z",
33
+ resources: [
34
+ {
35
+ listedType: "AWS::S3::Bucket",
36
+ location: "us-east-1",
37
+ nativeId: "assets-bucket",
38
+ cloudIds: [{ kind: "import-id", value: "assets-bucket" }],
39
+ tags: { team: "web", "api-token": "<masked>" },
40
+ properties: { BucketName: "assets-bucket", Arn: "arn:aws:s3:::assets-bucket", Tags: [{ Key: "team", Value: "web" }] },
41
+ observedAt: "2026-10-26T09:14:04.000Z",
42
+ facts: { cloudIds: [{ kind: "import-id", value: "assets-bucket" }], observedAt: "2026-10-26T09:14:04.000Z", markers: { owner: "web", encoding: "plain", source: "live" } },
43
+ },
44
+ {
45
+ listedType: "AWS::Pipes::Pipe",
46
+ location: "us-east-1",
47
+ nativeId: "orders-pipe",
48
+ cloudIds: [{ kind: "import-id", value: "orders-pipe" }],
49
+ tags: {},
50
+ properties: { Name: "orders-pipe", SourceParameters: { ClientSecret: "plain-text-if-the-lister-missed-it" } },
51
+ observedAt: "2026-10-26T09:14:05.000Z",
52
+ },
53
+ ],
54
+ coverage: [
55
+ { listedType: "AWS::S3::Bucket", location: "us-east-1", verdict: "listed" },
56
+ { listedType: "AWS::Pipes::Pipe", location: "us-east-1", verdict: "listed" },
57
+ ],
58
+ notes: [{ code: "default-types", message: "types * listed the default set" }],
59
+ ...overrides,
60
+ };
61
+ }
62
+
63
+ describe("typeListing", () => {
64
+ test("a mapped type becomes a typed node with its lexicon's normalization", () => {
65
+ const doc = typeListing(listing(), lexicons);
66
+ const bucket = doc.graph.nodes.find((n) => n.listedType === "AWS::S3::Bucket")!;
67
+ expect(bucket).toMatchObject({
68
+ id: "aws/123456789012/us-east-1/AWS::S3::Bucket/assets-bucket",
69
+ kind: "AWS::S3::Bucket",
70
+ lexicon: "aws",
71
+ location: "us-east-1",
72
+ nativeId: "assets-bucket",
73
+ physicalId: "assets-bucket",
74
+ });
75
+ expect(bucket.unmappedKind).toBeUndefined();
76
+ // The hook pruned Arn; the rest is key-sorted as the deep path leaves it.
77
+ expect(bucket.attrs).toEqual({ BucketName: "assets-bucket", Tags: [{ Key: "team", Value: "web" }] });
78
+ expect(bucket.facts).toEqual({
79
+ markers: { owner: "web", encoding: "plain", source: "live" },
80
+ cloudIds: [{ kind: "import-id", value: "assets-bucket" }],
81
+ observedAt: "2026-10-26T09:14:04.000Z",
82
+ });
83
+ });
84
+
85
+ test("an unmapped type is kept as an unknown-kind node, never dropped, and named in a note", () => {
86
+ const input = listing();
87
+ const doc = typeListing(input, lexicons);
88
+ // Every listed object is a node: the count is the guard against a drop.
89
+ expect(doc.graph.nodes).toHaveLength(input.resources.length);
90
+ const pipe = doc.graph.nodes.find((n) => n.listedType === "AWS::Pipes::Pipe");
91
+ expect(pipe).toBeDefined();
92
+ expect(pipe).toMatchObject({ kind: "AWS::Pipes::Pipe", lexicon: "aws", unmappedKind: true, nativeId: "orders-pipe" });
93
+ expect(doc.notes?.filter((n) => n.kind === "unmapped-type").map((n) => n.listedType)).toEqual(["AWS::Pipes::Pipe"]);
94
+ });
95
+
96
+ test("secrets are masked with the snapshot path's one list, on mapped and unmapped nodes and on tags", () => {
97
+ const doc = typeListing(listing(), lexicons);
98
+ const pipe = doc.graph.nodes.find((n) => n.listedType === "AWS::Pipes::Pipe")!;
99
+ expect((pipe.attrs.SourceParameters as Record<string, unknown>).ClientSecret).toBe(MASKED);
100
+ const bucket = doc.graph.nodes.find((n) => n.listedType === "AWS::S3::Bucket")!;
101
+ expect(bucket.tags).toEqual({ "api-token": MASKED, team: "web" });
102
+ expect(JSON.stringify(doc)).not.toContain("plain-text-if-the-lister-missed-it");
103
+ });
104
+
105
+ test("a lexicon's observed-tree reshape runs before its hooks", () => {
106
+ const doc = typeListing(
107
+ listing({
108
+ scope: { substrate: "kubernetes", scopeId: "kind-dev", locations: "*", types: "*" },
109
+ resources: [
110
+ {
111
+ listedType: "apps/v1/Deployment",
112
+ location: "web",
113
+ nativeId: "web/frontend",
114
+ cloudIds: [{ kind: "k8s-uid", value: "0d6f..." }],
115
+ tags: {},
116
+ properties: { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "frontend", namespace: "web" }, spec: { replicas: 2 } },
117
+ observedAt: "2026-10-26T09:14:04.000Z",
118
+ },
119
+ ],
120
+ coverage: [{ listedType: "*", location: "*", verdict: "listed" }],
121
+ notes: [],
122
+ }),
123
+ lexicons,
124
+ );
125
+ const [deploy] = doc.graph.nodes;
126
+ expect(deploy.id).toBe("kubernetes/kind-dev/web/apps%2Fv1%2FDeployment/web/frontend");
127
+ expect(deploy.kind).toBe("K8s::Apps::Deployment");
128
+ expect(deploy.attrs).toEqual({ metadata: { name: "frontend", namespace: "web" }, spec: { replicas: 2 } });
129
+ });
130
+
131
+ test("the document is stamped with the listing's scope and validates", () => {
132
+ const doc = typeListing(listing(), lexicons);
133
+ expect(() => validateIndexDocument(doc)).not.toThrow();
134
+ expect(doc.stamp.source).toEqual({ kind: "cloud", name: "behold-lister", version: "0.0.0-test" });
135
+ expect(doc.stamp.observedAt).toBe("2026-10-26T09:14:03.120Z");
136
+ expect(doc.stamp.scope).toEqual({ kind: "cloud", substrate: "aws", scopeId: "123456789012", locations: ["us-east-1"], types: "*" });
137
+ expect(doc.graph.edges).toEqual([]);
138
+ expect(doc.notes?.map((n) => n.kind)).toEqual(["default-types", "unmapped-type"]);
139
+ });
140
+
141
+ test("the same listing in another order gives the same document", () => {
142
+ const a = listing();
143
+ const b = listing({ resources: [...a.resources].reverse() });
144
+ expect(typeListing(b, [...lexicons].reverse())).toEqual(typeListing(a, lexicons));
145
+ });
146
+
147
+ test("the node id matches behold's listedNodeId: % and / escaped in the first four parts, the native id raw", () => {
148
+ expect(
149
+ listedNodeId({ substrate: "azure", scopeId: "sub-1", location: "eastus", listedType: "microsoft.storage/storageaccounts", nativeId: "/subscriptions/sub-1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/st1" }),
150
+ ).toBe("azure/sub-1/eastus/microsoft.storage%2Fstorageaccounts//subscriptions/sub-1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/st1");
151
+ });
152
+ });
153
+
154
+ describe("indexCoverageFor", () => {
155
+ test('"*" matches every type or location, and an exact entry wins', () => {
156
+ const coverage = [
157
+ { listedType: "*", location: "*", verdict: "listed" as const },
158
+ { listedType: "microsoft.web/sites", location: "*", verdict: "no-credentials" as const },
159
+ { listedType: "microsoft.web/sites", location: "westeurope", verdict: "read-failed" as const },
160
+ ];
161
+ expect(indexCoverageFor(coverage, "microsoft.storage/storageaccounts", "eastus")?.verdict).toBe("listed");
162
+ expect(indexCoverageFor(coverage, "microsoft.web/sites", "eastus")?.verdict).toBe("no-credentials");
163
+ expect(indexCoverageFor(coverage, "microsoft.web/sites", "westeurope")?.verdict).toBe("read-failed");
164
+ expect(indexCoverageFor([{ listedType: "AWS::S3::Bucket", location: "us-east-1", verdict: "listed" }], "AWS::S3::Bucket", "us-west-2")).toBeUndefined();
165
+ });
166
+ });
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Typed nodes for indexed resources (#4521, tier 1 of INTENTIUS/behold#1025).
3
+ *
4
+ * behold#1029's cloud lister reads one account, subscription, project or
5
+ * cluster through that cloud's inventory API and returns a
6
+ * {@link ListingResult}. {@link typeListing} turns it into an
7
+ * {@link IndexDocument}: each listed object becomes an `IRNode` whose `kind`
8
+ * is the lexicon kind that describes it, with its properties normalized by
9
+ * that lexicon's own deep-observe hooks, so an indexed tree has the shape an
10
+ * observed one has and the capability catalog (#4375) applies to it.
11
+ *
12
+ * Pure: no I/O and no clock. The lexicons come in as data (each plugin's
13
+ * `listedTypes` and `deepNormalizationHooks`), so behold passes the plugins
14
+ * it already loads and a test passes a table.
15
+ *
16
+ * Rules:
17
+ * - a listed type no lexicon maps stays a node of unknown kind: `kind` is
18
+ * the listed type, `lexicon` the substrate, `unmappedKind: true`. It is
19
+ * never dropped, and a note names each such type once;
20
+ * - secret-bearing values are masked with the one list the snapshot path
21
+ * uses (`isSensitiveKey`, ../sensitive-key.ts). The lister masked already;
22
+ * masking again here means no listing reaches the index unmasked;
23
+ * - a node id is the path-style id ruled on behold#1025 (Q2):
24
+ * `<substrate>/<scopeId>/<location>/<listedType>/<nativeId>`.
25
+ *
26
+ * Edges are #4522's (./derive-edges.ts); this module leaves `edges` empty.
27
+ */
28
+ import type { DeepNormalizationHooks } from "../deep-observation";
29
+ import { MASKED, isSensitiveKey, normalizeDeepProperties } from "../deep-observation";
30
+ import type { EngineFacts } from "../engine-facts";
31
+ import type { GraphIR, IRNode } from "../graph-ir";
32
+ import {
33
+ canonicalScope,
34
+ indexDocumentDigest,
35
+ indexScopeKey,
36
+ type CloudIndexScope,
37
+ type CoverageEntry,
38
+ type IndexDocument,
39
+ type IndexNote,
40
+ } from "./index-document";
41
+
42
+ /** The four substrates the lister reads, one per run (behold#1025 Q6). */
43
+ export type ListingSubstrate = CloudIndexScope["substrate"];
44
+
45
+ /** One run's scope, as the lister names it. `"*"` means every location or type. */
46
+ export interface CloudScope {
47
+ substrate: ListingSubstrate;
48
+ scopeId: string;
49
+ locations: readonly string[] | "*";
50
+ types: readonly string[] | "*";
51
+ tagFilter?: Readonly<Record<string, string>>;
52
+ }
53
+
54
+ /** A cloud id, kinds as #4520 ruled them (`EngineFacts.cloudIds`). */
55
+ export interface ListedCloudId {
56
+ kind: "import-id" | "arn" | "provider-id" | "self-link" | "k8s-uid";
57
+ value: string;
58
+ }
59
+
60
+ /** The facts a cloud listing can supply: markers read off tags, cloud ids, when it was read. */
61
+ export interface ListedFacts {
62
+ markers?: EngineFacts["markers"];
63
+ cloudIds: ListedCloudId[];
64
+ observedAt: string;
65
+ }
66
+
67
+ /** One listed object (behold#1025 `ListedResource`). */
68
+ export interface ListedResource {
69
+ /** The path-style id; recomputed by {@link listedNodeId}, so a listing may leave it out. */
70
+ id?: string;
71
+ listedType: string;
72
+ location: string;
73
+ nativeId: string;
74
+ cloudIds: ListedCloudId[];
75
+ tags: Record<string, string>;
76
+ /** As returned, unnormalized, already masked by the lister. */
77
+ properties: Record<string, unknown>;
78
+ observedAt: string;
79
+ facts?: ListedFacts;
80
+ }
81
+
82
+ /** A lister note: a type with no lister, a disabled API, a tag filter. */
83
+ export interface ListingNote {
84
+ code: string;
85
+ message: string;
86
+ listedType?: string;
87
+ location?: string;
88
+ command?: string;
89
+ }
90
+
91
+ /** The lister's whole output for one run (behold#1025 `ListingResult`, behold#1029). */
92
+ export interface ListingResult {
93
+ listing: "v1";
94
+ source?: { name: string; version?: string };
95
+ scope: CloudScope;
96
+ startedAt: string;
97
+ completedAt: string;
98
+ resources: ListedResource[];
99
+ coverage: CoverageEntry[];
100
+ notes: ListingNote[];
101
+ }
102
+
103
+ /**
104
+ * How a lexicon reads one substrate's listed types (a plugin's
105
+ * `listedTypes`). Data plus two pure functions.
106
+ */
107
+ export interface ListedTypeMapping {
108
+ substrate: ListingSubstrate;
109
+ /** The lexicon kind for a listed type, or undefined when the lexicon has none. */
110
+ kindFor(listedType: string): string | undefined;
111
+ /**
112
+ * The listed properties reshaped into the tree this lexicon's deep reader
113
+ * returns, before the hooks run: Azure flattens `properties.*` beside
114
+ * `location` and `sku`, Kubernetes drops `apiVersion` and `kind`. Absent
115
+ * means the listing's shape already is that tree (Cloud Control's
116
+ * CloudFormation model).
117
+ */
118
+ observedTree?(properties: Record<string, unknown>, context: { listedType: string; kind: string }): Record<string, unknown>;
119
+ }
120
+
121
+ /** What {@link typeListing} needs from a lexicon. A `LexiconPlugin` is one. */
122
+ export interface ListingLexicon {
123
+ name: string;
124
+ listedTypes?: ListedTypeMapping;
125
+ deepNormalizationHooks?: DeepNormalizationHooks;
126
+ }
127
+
128
+ function escapeSegment(s: string): string {
129
+ return s.replace(/%/g, "%25").replace(/\//g, "%2F");
130
+ }
131
+
132
+ /**
133
+ * `<substrate>/<scopeId>/<location>/<listedType>/<nativeId>` (behold#1025
134
+ * Q2), exactly as behold's lister writes it (src/cloud-lister/ids.ts): `%`
135
+ * and `/` are escaped in the first four parts, and the native id is last and
136
+ * unescaped, so everything after the fourth `/` is it.
137
+ */
138
+ export function listedNodeId(parts: { substrate: string; scopeId: string; location: string; listedType: string; nativeId: string }): string {
139
+ return [
140
+ escapeSegment(parts.substrate),
141
+ escapeSegment(parts.scopeId),
142
+ escapeSegment(parts.location),
143
+ escapeSegment(parts.listedType),
144
+ parts.nativeId,
145
+ ].join("/");
146
+ }
147
+
148
+ /** The lexicon (and kind) for one listed type: the first mapping, by lexicon name, that answers. */
149
+ export function resolveListedKind(
150
+ substrate: ListingSubstrate,
151
+ listedType: string,
152
+ lexicons: readonly ListingLexicon[],
153
+ ): { lexicon: ListingLexicon; kind: string } | undefined {
154
+ for (const lexicon of lexicons) {
155
+ const mapping = lexicon.listedTypes;
156
+ if (!mapping || mapping.substrate !== substrate) continue;
157
+ const kind = mapping.kindFor(listedType);
158
+ if (kind !== undefined) return { lexicon, kind };
159
+ }
160
+ return undefined;
161
+ }
162
+
163
+ function maskTags(tags: Record<string, string>): Record<string, string> {
164
+ const out: Record<string, string> = {};
165
+ for (const k of Object.keys(tags).sort()) out[k] = isSensitiveKey(k) ? MASKED : tags[k];
166
+ return out;
167
+ }
168
+
169
+ const byName = (a: ListingLexicon, b: ListingLexicon) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
170
+
171
+ /** One listed object as a typed (or unknown-kind) node. */
172
+ export function typeListedResource(
173
+ scope: CloudScope,
174
+ resource: ListedResource,
175
+ lexicons: readonly ListingLexicon[],
176
+ ): IRNode {
177
+ const sorted = [...lexicons].sort(byName);
178
+ const resolved = resolveListedKind(scope.substrate, resource.listedType, sorted);
179
+ const id = listedNodeId({
180
+ substrate: scope.substrate,
181
+ scopeId: scope.scopeId,
182
+ location: resource.location,
183
+ listedType: resource.listedType,
184
+ nativeId: resource.nativeId,
185
+ });
186
+ let kind: string;
187
+ let lexicon: string;
188
+ let attrs: Record<string, unknown>;
189
+ if (resolved) {
190
+ kind = resolved.kind;
191
+ lexicon = resolved.lexicon.name;
192
+ const reshape = resolved.lexicon.listedTypes?.observedTree;
193
+ const tree = reshape ? reshape(resource.properties, { listedType: resource.listedType, kind }) : resource.properties;
194
+ attrs = normalizeDeepProperties(tree, { entityType: kind, side: "live", hooks: resolved.lexicon.deepNormalizationHooks });
195
+ } else {
196
+ kind = resource.listedType;
197
+ lexicon = scope.substrate;
198
+ // No lexicon, so no hooks: the listing's properties as returned, key-sorted and masked.
199
+ attrs = normalizeDeepProperties(resource.properties, { entityType: resource.listedType, side: "live" });
200
+ }
201
+ const cloudIds = resource.cloudIds.length > 0 ? resource.cloudIds : resource.facts?.cloudIds ?? [];
202
+ const facts: EngineFacts = {
203
+ ...(resource.facts?.markers ? { markers: resource.facts.markers } : {}),
204
+ ...(cloudIds.length > 0 ? { cloudIds: cloudIds.map((c) => ({ kind: c.kind, value: c.value })) } : {}),
205
+ observedAt: resource.facts?.observedAt ?? resource.observedAt,
206
+ };
207
+ return {
208
+ id,
209
+ kind,
210
+ lexicon,
211
+ attrs,
212
+ physicalId: resource.nativeId,
213
+ facts,
214
+ listedType: resource.listedType,
215
+ location: resource.location,
216
+ nativeId: resource.nativeId,
217
+ tags: maskTags(resource.tags ?? {}),
218
+ ...(resolved ? {} : { unmappedKind: true }),
219
+ };
220
+ }
221
+
222
+ /** The listing's scope as the index stamp's cloud scope. */
223
+ export function listingIndexScope(scope: CloudScope): CloudIndexScope {
224
+ return canonicalScope({
225
+ kind: "cloud",
226
+ substrate: scope.substrate,
227
+ scopeId: scope.scopeId,
228
+ locations: scope.locations,
229
+ types: scope.types,
230
+ ...(scope.tagFilter ? { tagFilter: scope.tagFilter } : {}),
231
+ }) as CloudIndexScope;
232
+ }
233
+
234
+ /** Every listed object as a node, sorted by id. */
235
+ export function typeListingNodes(listing: ListingResult, lexicons: readonly ListingLexicon[]): IRNode[] {
236
+ const nodes = listing.resources.map((r) => typeListedResource(listing.scope, r, lexicons));
237
+ nodes.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
238
+ return nodes;
239
+ }
240
+
241
+ const compareNotes = (a: IndexNote, b: IndexNote): number => {
242
+ const ka = `${a.kind}\u0000${a.listedType ?? ""}\u0000${a.location ?? ""}\u0000${a.message}`;
243
+ const kb = `${b.kind}\u0000${b.listedType ?? ""}\u0000${b.location ?? ""}\u0000${b.message}`;
244
+ return ka < kb ? -1 : ka > kb ? 1 : 0;
245
+ };
246
+
247
+ /**
248
+ * The listing as one index version (behold#1025 schema). `graph.edges` is
249
+ * empty; chant#4522's `deriveEdges` fills it. Coverage passes through as the
250
+ * lister wrote it, wildcards included. The lister's notes keep their code as
251
+ * `kind`, and each unmapped listed type gets one `unmapped-type` note.
252
+ */
253
+ export function typeListing(listing: ListingResult, lexicons: readonly ListingLexicon[]): IndexDocument {
254
+ const nodes = typeListingNodes(listing, lexicons);
255
+ const graph: GraphIR = {
256
+ version: 1,
257
+ nodes,
258
+ edges: [],
259
+ groups: { byLexicon: groupByLexicon(nodes) },
260
+ };
261
+ const coverage = [...listing.coverage].map((c) => ({ ...c }));
262
+ const notes: IndexNote[] = listing.notes.map((n) => ({
263
+ kind: n.code,
264
+ message: n.message,
265
+ ...(n.listedType !== undefined ? { listedType: n.listedType } : {}),
266
+ ...(n.location !== undefined ? { location: n.location } : {}),
267
+ ...(n.command !== undefined ? { command: n.command } : {}),
268
+ }));
269
+ const unmapped = [...new Set(nodes.filter((n) => n.unmappedKind).map((n) => n.listedType!))].sort();
270
+ for (const t of unmapped) {
271
+ notes.push({
272
+ kind: "unmapped-type",
273
+ listedType: t,
274
+ message: `no lexicon kind maps the listed type ${t}; its objects are kept as nodes of unknown kind, with the listing's properties masked`,
275
+ });
276
+ }
277
+ notes.sort(compareNotes);
278
+ return assembleIndexDocument(listing, graph, coverage, notes);
279
+ }
280
+
281
+ /** Stamp a graph, coverage and notes read from `listing` as one index version. */
282
+ export function assembleIndexDocument(
283
+ listing: ListingResult,
284
+ graph: GraphIR,
285
+ coverage: CoverageEntry[],
286
+ notes: IndexNote[],
287
+ ): IndexDocument {
288
+ const scope = listingIndexScope(listing.scope);
289
+ return {
290
+ index: "v1",
291
+ stamp: {
292
+ source: { kind: "cloud", name: listing.source?.name ?? "behold-lister", ...(listing.source?.version ? { version: listing.source.version } : {}) },
293
+ scope,
294
+ scopeKey: indexScopeKey(scope),
295
+ observedAt: listing.startedAt,
296
+ completedAt: listing.completedAt,
297
+ digest: indexDocumentDigest({ graph, coverage }),
298
+ },
299
+ coverage,
300
+ graph,
301
+ ...(notes.length > 0 ? { notes } : {}),
302
+ };
303
+ }
304
+
305
+ function groupByLexicon(nodes: readonly IRNode[]): Record<string, string[]> {
306
+ const out: Record<string, string[]> = {};
307
+ for (const n of nodes) (out[n.lexicon] ??= []).push(n.id);
308
+ return Object.fromEntries(Object.keys(out).sort().map((k) => [k, out[k].sort()]));
309
+ }
@@ -57,6 +57,21 @@ export interface ComponentCheckContext {
57
57
  * `none-by-design`, i.e. flags nothing).
58
58
  */
59
59
  rollbackPolicies?: ReadonlyMap<string, RollbackPolicy>;
60
+ /**
61
+ * The project's `terraform.roots` entries marked `signalOnly` (#4484),
62
+ * keyed by root name. Supplied by `chant lint` from the project's config
63
+ * (`signalOnlyRootsOf` in ./rules/comp/comp008-applies-signal-only-root.ts);
64
+ * undefined when none was resolved, in which case COMP008 flags nothing.
65
+ */
66
+ signalOnlyRoots?: ReadonlyMap<string, SignalOnlyRoot>;
67
+ }
68
+
69
+ /** One `terraform.roots` entry marked `signalOnly` (#4490). */
70
+ export interface SignalOnlyRoot {
71
+ /** The root's `dir` as the config writes it. */
72
+ dir: string;
73
+ /** The workspace member whose directory holds the root, when the workspace reads. */
74
+ owner?: string;
60
75
  }
61
76
 
62
77
  /** A diagnostic from a component composition check. Shaped like `PostSynthDiagnostic`, plus a `file` so it can be merged into `LintDiagnostic`s and reported per-file like every other lint diagnostic. */
@@ -104,7 +119,7 @@ export interface ComponentCheck {
104
119
  export async function runComponentChecks(
105
120
  path: string,
106
121
  checks: ComponentCheck[],
107
- registryContext?: Pick<ComponentCheckContext, "knownKinds" | "rollbackPolicies">,
122
+ registryContext?: Pick<ComponentCheckContext, "knownKinds" | "rollbackPolicies" | "signalOnlyRoots">,
108
123
  sandbox?: boolean,
109
124
  buildParams?: BuildParamProvenance[],
110
125
  ): Promise<ComponentCheckDiagnostic[]> {
@@ -137,6 +152,7 @@ export async function runComponentChecks(
137
152
  components,
138
153
  knownKinds: registryContext?.knownKinds,
139
154
  rollbackPolicies: registryContext?.rollbackPolicies,
155
+ signalOnlyRoots: registryContext?.signalOnlyRoots,
140
156
  };
141
157
  for (const check of checks) {
142
158
  diagnostics.push(...check.check(ctx));
@@ -424,6 +424,7 @@ describe("loadConfig", () => {
424
424
  COMP005: "error",
425
425
  COMP006: "error",
426
426
  COMP007: "warning",
427
+ COMP008: "warning",
427
428
  });
428
429
  });
429
430
 
@@ -474,6 +475,7 @@ describe("loadConfig", () => {
474
475
  COMP005: "off",
475
476
  COMP006: "off",
476
477
  COMP007: "off",
478
+ COMP008: "warning",
477
479
  });
478
480
  });
479
481
 
@@ -511,6 +513,7 @@ describe("loadConfig", () => {
511
513
  COMP005: "error",
512
514
  COMP006: "error",
513
515
  COMP007: "warning",
516
+ COMP008: "warning",
514
517
  });
515
518
  });
516
519
 
@@ -21,6 +21,7 @@
21
21
  "COMP004": "warning",
22
22
  "COMP005": "off",
23
23
  "COMP006": "off",
24
- "COMP007": "off"
24
+ "COMP007": "off",
25
+ "COMP008": "warning"
25
26
  }
26
27
  }
@@ -21,6 +21,7 @@
21
21
  "COMP004": "error",
22
22
  "COMP005": "error",
23
23
  "COMP006": "error",
24
- "COMP007": "warning"
24
+ "COMP007": "warning",
25
+ "COMP008": "warning"
25
26
  }
26
27
  }
@@ -0,0 +1,15 @@
1
+ import type { Component } from "../../../../../../components/component";
2
+ import { phase } from "../../../../../../components/component";
3
+
4
+ /** COMP008 fail case: a terraform-apply step deploys "azure", which the test marks signalOnly. */
5
+ export const dns: Component = {
6
+ name: "dns",
7
+ archetype: "infra",
8
+ dependsOn: [],
9
+ deploy: [
10
+ phase("Apply", [
11
+ { kind: "terraform-apply", root: "dns" },
12
+ { kind: "terraform-apply", root: "azure" },
13
+ ]),
14
+ ],
15
+ };