@intentius/chant 0.124.52 → 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 (584) 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 +15 -2
  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 +215 -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/declared-reads.d.ts +38 -0
  120. package/dist/components/declared-reads.d.ts.map +1 -0
  121. package/dist/components/declared-reads.js +131 -0
  122. package/dist/components/deploy-units.d.ts.map +1 -1
  123. package/dist/components/deploy-units.js +4 -0
  124. package/dist/components/driver.d.ts +34 -1
  125. package/dist/components/driver.d.ts.map +1 -1
  126. package/dist/components/driver.js +66 -3
  127. package/dist/components/fan-out-run.d.ts +23 -1
  128. package/dist/components/fan-out-run.d.ts.map +1 -1
  129. package/dist/components/fan-out-run.js +82 -14
  130. package/dist/components/fan-out-support.d.ts +6 -0
  131. package/dist/components/fan-out-support.d.ts.map +1 -1
  132. package/dist/components/fan-out-support.js +4 -1
  133. package/dist/components/fan-out.d.ts +28 -0
  134. package/dist/components/fan-out.d.ts.map +1 -1
  135. package/dist/components/groups.d.ts +229 -0
  136. package/dist/components/groups.d.ts.map +1 -0
  137. package/dist/components/groups.js +760 -0
  138. package/dist/components/index.d.ts +6 -2
  139. package/dist/components/index.d.ts.map +1 -1
  140. package/dist/components/index.js +4 -0
  141. package/dist/components/plan-shape-conformance.d.ts +48 -0
  142. package/dist/components/plan-shape-conformance.d.ts.map +1 -0
  143. package/dist/components/plan-shape-conformance.js +122 -0
  144. package/dist/components/pr-run.d.ts +41 -4
  145. package/dist/components/pr-run.d.ts.map +1 -1
  146. package/dist/components/pr-run.js +209 -39
  147. package/dist/components/promote-rounds.d.ts +9 -0
  148. package/dist/components/promote-rounds.d.ts.map +1 -1
  149. package/dist/components/promote-rounds.js +23 -2
  150. package/dist/components/promote.d.ts +17 -0
  151. package/dist/components/promote.d.ts.map +1 -1
  152. package/dist/components/promote.js +36 -6
  153. package/dist/components/release-gate.d.ts +23 -1
  154. package/dist/components/release-gate.d.ts.map +1 -1
  155. package/dist/components/release-gate.js +97 -33
  156. package/dist/components/run-groups.d.ts +58 -0
  157. package/dist/components/run-groups.d.ts.map +1 -0
  158. package/dist/components/run-groups.js +78 -0
  159. package/dist/components/starter-plugin.d.ts +1 -0
  160. package/dist/components/starter-plugin.d.ts.map +1 -1
  161. package/dist/components/starter-plugin.js +6 -1
  162. package/dist/components/verbs/index.d.ts +1 -0
  163. package/dist/components/verbs/index.d.ts.map +1 -1
  164. package/dist/components/verbs/index.js +1 -0
  165. package/dist/components/verbs/member-outputs.d.ts +187 -0
  166. package/dist/components/verbs/member-outputs.d.ts.map +1 -0
  167. package/dist/components/verbs/member-outputs.js +218 -0
  168. package/dist/config.d.ts +60 -0
  169. package/dist/config.d.ts.map +1 -1
  170. package/dist/config.js +27 -0
  171. package/dist/deep-observation.d.ts +1 -13
  172. package/dist/deep-observation.d.ts.map +1 -1
  173. package/dist/deep-observation.js +5 -22
  174. package/dist/drift-record.d.ts +542 -0
  175. package/dist/drift-record.d.ts.map +1 -0
  176. package/dist/drift-record.js +886 -0
  177. package/dist/engine-facts.d.ts +144 -0
  178. package/dist/engine-facts.d.ts.map +1 -0
  179. package/dist/engine-facts.js +14 -0
  180. package/dist/graph-ir.d.ts +153 -2
  181. package/dist/graph-ir.d.ts.map +1 -1
  182. package/dist/graph-ir.js +224 -6
  183. package/dist/graph-refs.d.ts +145 -0
  184. package/dist/graph-refs.d.ts.map +1 -1
  185. package/dist/graph-refs.js +305 -0
  186. package/dist/graph-schema/fixtures/adopt.json +15 -0
  187. package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  188. package/dist/graph-schema/fixtures/index.json +68 -0
  189. package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  190. package/dist/graph-schema/fixtures.d.ts +23 -0
  191. package/dist/graph-schema/fixtures.d.ts.map +1 -0
  192. package/dist/graph-schema/fixtures.js +246 -0
  193. package/dist/graph-schema/generate.d.ts +15 -0
  194. package/dist/graph-schema/generate.d.ts.map +1 -0
  195. package/dist/graph-schema/generate.js +267 -0
  196. package/dist/graph-schema/graph.schema.json +1387 -0
  197. package/dist/health.schema.json +2 -1
  198. package/dist/identity.d.ts.map +1 -1
  199. package/dist/identity.js +2 -0
  200. package/dist/index.d.ts +1 -0
  201. package/dist/index.d.ts.map +1 -1
  202. package/dist/index.js +2 -0
  203. package/dist/instance-nodes.d.ts +159 -0
  204. package/dist/instance-nodes.d.ts.map +1 -0
  205. package/dist/instance-nodes.js +231 -0
  206. package/dist/lexicon.d.ts +72 -0
  207. package/dist/lexicon.d.ts.map +1 -1
  208. package/dist/lifecycle/adoption-file.d.ts +56 -0
  209. package/dist/lifecycle/adoption-file.d.ts.map +1 -0
  210. package/dist/lifecycle/adoption-file.js +123 -0
  211. package/dist/lifecycle/cloud-index.d.ts +186 -0
  212. package/dist/lifecycle/cloud-index.d.ts.map +1 -0
  213. package/dist/lifecycle/cloud-index.js +618 -0
  214. package/dist/lifecycle/derive-edges.d.ts +59 -0
  215. package/dist/lifecycle/derive-edges.d.ts.map +1 -0
  216. package/dist/lifecycle/derive-edges.js +126 -0
  217. package/dist/lifecycle/git.d.ts +10 -9
  218. package/dist/lifecycle/git.d.ts.map +1 -1
  219. package/dist/lifecycle/git.js +7 -1
  220. package/dist/lifecycle/identity-match.d.ts +133 -0
  221. package/dist/lifecycle/identity-match.d.ts.map +1 -0
  222. package/dist/lifecycle/identity-match.js +147 -0
  223. package/dist/lifecycle/index-diff.d.ts +224 -0
  224. package/dist/lifecycle/index-diff.d.ts.map +1 -0
  225. package/dist/lifecycle/index-diff.js +327 -0
  226. package/dist/lifecycle/index-document.d.ts +130 -0
  227. package/dist/lifecycle/index-document.d.ts.map +1 -0
  228. package/dist/lifecycle/index-document.js +189 -0
  229. package/dist/lifecycle/index-drift.d.ts +121 -0
  230. package/dist/lifecycle/index-drift.d.ts.map +1 -0
  231. package/dist/lifecycle/index-drift.js +178 -0
  232. package/dist/lifecycle/index-import.d.ts +168 -0
  233. package/dist/lifecycle/index-import.d.ts.map +1 -0
  234. package/dist/lifecycle/index-import.js +515 -0
  235. package/dist/lifecycle/index-join.d.ts +129 -0
  236. package/dist/lifecycle/index-join.d.ts.map +1 -0
  237. package/dist/lifecycle/index-join.js +248 -0
  238. package/dist/lifecycle/index.d.ts +4 -0
  239. package/dist/lifecycle/index.d.ts.map +1 -1
  240. package/dist/lifecycle/index.js +4 -0
  241. package/dist/lifecycle/observation-baseline.d.ts +61 -21
  242. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  243. package/dist/lifecycle/observation-baseline.js +144 -19
  244. package/dist/lifecycle/observe.d.ts.map +1 -1
  245. package/dist/lifecycle/observe.js +6 -1
  246. package/dist/lifecycle/query.d.ts +45 -4
  247. package/dist/lifecycle/query.d.ts.map +1 -1
  248. package/dist/lifecycle/query.js +89 -7
  249. package/dist/lifecycle/replay.d.ts +10 -3
  250. package/dist/lifecycle/replay.d.ts.map +1 -1
  251. package/dist/lifecycle/replay.js +10 -6
  252. package/dist/lifecycle/scenario.d.ts +4 -3
  253. package/dist/lifecycle/scenario.d.ts.map +1 -1
  254. package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
  255. package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
  256. package/dist/lifecycle/snapshot-store-fake.js +21 -0
  257. package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
  258. package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
  259. package/dist/lifecycle/snapshot-store-s3.js +254 -0
  260. package/dist/lifecycle/snapshot-store.d.ts +170 -0
  261. package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
  262. package/dist/lifecycle/snapshot-store.js +832 -0
  263. package/dist/lifecycle/snapshot.d.ts +11 -0
  264. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  265. package/dist/lifecycle/snapshot.js +18 -5
  266. package/dist/lifecycle/type-listing.d.ts +148 -0
  267. package/dist/lifecycle/type-listing.d.ts.map +1 -0
  268. package/dist/lifecycle/type-listing.js +166 -0
  269. package/dist/lint/component-checks.d.ts +15 -1
  270. package/dist/lint/component-checks.d.ts.map +1 -1
  271. package/dist/lint/component-checks.js +1 -0
  272. package/dist/lint/presets/relaxed.json +2 -1
  273. package/dist/lint/presets/strict.json +2 -1
  274. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
  275. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
  276. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
  277. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
  278. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
  279. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
  280. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
  281. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
  282. package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
  283. package/dist/lint/rules/comp/index.d.ts +2 -1
  284. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  285. package/dist/lint/rules/comp/index.js +4 -1
  286. package/dist/observation.d.ts +19 -2
  287. package/dist/observation.d.ts.map +1 -1
  288. package/dist/observation.js +18 -1
  289. package/dist/op/activities/activity-contracts.d.ts +1 -1
  290. package/dist/op/activities/activity-contracts.js +1 -1
  291. package/dist/op/activities/change-set.d.ts +6 -4
  292. package/dist/op/activities/change-set.d.ts.map +1 -1
  293. package/dist/op/activities/change-set.js +4 -2
  294. package/dist/op/activity-failure.d.ts +27 -0
  295. package/dist/op/activity-failure.d.ts.map +1 -1
  296. package/dist/op/activity-failure.js +31 -0
  297. package/dist/op/index.d.ts +2 -1
  298. package/dist/op/index.d.ts.map +1 -1
  299. package/dist/op/index.js +1 -1
  300. package/dist/op/local-executor.d.ts +5 -1
  301. package/dist/op/local-executor.d.ts.map +1 -1
  302. package/dist/op/local-executor.js +22 -8
  303. package/dist/pr-loop.d.ts +56 -1
  304. package/dist/pr-loop.d.ts.map +1 -1
  305. package/dist/pr-loop.js +51 -1
  306. package/dist/pulumi-urn.d.ts +101 -0
  307. package/dist/pulumi-urn.d.ts.map +1 -0
  308. package/dist/pulumi-urn.js +131 -0
  309. package/dist/release-floors.d.ts +23 -0
  310. package/dist/release-floors.d.ts.map +1 -0
  311. package/dist/release-floors.js +22 -0
  312. package/dist/sensitive-key.d.ts +23 -0
  313. package/dist/sensitive-key.d.ts.map +1 -0
  314. package/dist/sensitive-key.js +31 -0
  315. package/dist/wave-runtime.mjs +234 -234
  316. package/dist/workspace/adoption-report.d.ts +53 -0
  317. package/dist/workspace/adoption-report.d.ts.map +1 -0
  318. package/dist/workspace/adoption-report.js +76 -0
  319. package/dist/workspace/capabilities-contract.d.ts +7 -0
  320. package/dist/workspace/capabilities-contract.d.ts.map +1 -1
  321. package/dist/workspace/capabilities-contract.js +2 -1
  322. package/dist/workspace/capabilities.schema.json +98 -0
  323. package/dist/workspace/change-set.schema.json +42 -3
  324. package/dist/workspace/ci-cloud-identity.d.ts +13 -1
  325. package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
  326. package/dist/workspace/ci-cloud-identity.js +30 -1
  327. package/dist/workspace/compose-graph.d.ts +10 -1
  328. package/dist/workspace/compose-graph.d.ts.map +1 -1
  329. package/dist/workspace/compose-graph.js +6 -1
  330. package/dist/workspace/conformance/index.d.ts +1 -1
  331. package/dist/workspace/conformance/index.d.ts.map +1 -1
  332. package/dist/workspace/conformance/index.js +6 -1
  333. package/dist/workspace/drift-cli.d.ts +81 -0
  334. package/dist/workspace/drift-cli.d.ts.map +1 -0
  335. package/dist/workspace/drift-cli.js +317 -0
  336. package/dist/workspace/drift.schema.json +672 -0
  337. package/dist/workspace/graph-cli.d.ts +2 -0
  338. package/dist/workspace/graph-cli.d.ts.map +1 -1
  339. package/dist/workspace/graph-cli.js +3 -0
  340. package/dist/workspace/graph.schema.json +72 -0
  341. package/dist/workspace/init.d.ts +5 -1
  342. package/dist/workspace/init.d.ts.map +1 -1
  343. package/dist/workspace/init.js +55 -9
  344. package/dist/workspace/ledger.schema.json +1 -1
  345. package/dist/workspace/nested-graph.d.ts.map +1 -1
  346. package/dist/workspace/nested-graph.js +2 -0
  347. package/dist/workspace/pr-report.schema.json +51 -2
  348. package/dist/workspace/promote-waves.d.ts +112 -0
  349. package/dist/workspace/promote-waves.d.ts.map +1 -0
  350. package/dist/workspace/promote-waves.js +290 -0
  351. package/dist/workspace/promote.d.ts +84 -2
  352. package/dist/workspace/promote.d.ts.map +1 -1
  353. package/dist/workspace/promote.js +347 -61
  354. package/dist/workspace/reason-codes.d.ts +12 -0
  355. package/dist/workspace/reason-codes.d.ts.map +1 -1
  356. package/dist/workspace/reason-codes.js +14 -0
  357. package/dist/workspace/search-contract.d.ts +3 -3
  358. package/dist/workspace/search-contract.d.ts.map +1 -1
  359. package/dist/workspace/search-contract.js +2 -0
  360. package/dist/workspace/search.schema.json +103 -5
  361. package/package.json +14 -1
  362. package/src/__fixtures__/drift-record/README.md +58 -0
  363. package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
  364. package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
  365. package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
  366. package/src/__fixtures__/drift-record/component-status.json +91 -0
  367. package/src/__fixtures__/drift-record/deep-diff.json +259 -0
  368. package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
  369. package/src/__fixtures__/drift-record/live-diff.json +125 -0
  370. package/src/audit/discover.e2e.test.ts +14 -0
  371. package/src/audit/discover.ts +3 -1
  372. package/src/behaviour.ts +1 -1
  373. package/src/capabilities/templates.json +145 -0
  374. package/src/capabilities/templates.test.ts +212 -0
  375. package/src/capabilities/templates.ts +204 -0
  376. package/src/change-set-bundle.test.ts +83 -0
  377. package/src/change-set-graph.ts +80 -9
  378. package/src/change-set.test.ts +24 -0
  379. package/src/change-set.ts +77 -14
  380. package/src/ci-identity-targets.test.ts +23 -0
  381. package/src/ci-identity.ts +10 -4
  382. package/src/cli/commands/import-index.test.ts +117 -0
  383. package/src/cli/commands/import-index.ts +265 -0
  384. package/src/cli/commands/lint.ts +4 -2
  385. package/src/cli/handlers/components.test.ts +9 -0
  386. package/src/cli/handlers/components.ts +12 -2
  387. package/src/cli/handlers/fan-out.ts +17 -3
  388. package/src/cli/handlers/graph.test.ts +42 -0
  389. package/src/cli/handlers/graph.ts +26 -4
  390. package/src/cli/handlers/index-cloud.ts +32 -0
  391. package/src/cli/handlers/index-diff.test.ts +68 -0
  392. package/src/cli/handlers/index-diff.ts +146 -0
  393. package/src/cli/handlers/ledger-root-member.test.ts +161 -0
  394. package/src/cli/handlers/lifecycle-drift.ts +68 -0
  395. package/src/cli/handlers/lifecycle.test.ts +33 -11
  396. package/src/cli/handlers/lifecycle.ts +205 -30
  397. package/src/cli/handlers/misc.ts +73 -0
  398. package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
  399. package/src/cli/handlers/pr.ts +14 -2
  400. package/src/cli/handlers/promote.test.ts +64 -0
  401. package/src/cli/handlers/promote.ts +81 -6
  402. package/src/cli/handlers/query.ts +27 -4
  403. package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
  404. package/src/cli/handlers/run-groups-cli.test.ts +229 -0
  405. package/src/cli/handlers/scenario.test.ts +13 -0
  406. package/src/cli/handlers/scenario.ts +8 -7
  407. package/src/cli/handlers/search-index.test.ts +255 -0
  408. package/src/cli/handlers/search-json.test.ts +1 -1
  409. package/src/cli/handlers/search-ownership.test.ts +194 -0
  410. package/src/cli/handlers/search.ts +271 -38
  411. package/src/cli/main.test.ts +7 -0
  412. package/src/cli/main.ts +56 -1
  413. package/src/cli/mcp/lifecycle-tools.ts +3 -2
  414. package/src/cli/mcp/resource-handlers.ts +4 -3
  415. package/src/cli/mcp/workspace-tools.ts +1 -1
  416. package/src/cli/parse-args.ts +40 -2
  417. package/src/cli/registry.ts +19 -1
  418. package/src/cloud-lister/aws.test.ts +202 -0
  419. package/src/cloud-lister/aws.ts +226 -0
  420. package/src/cloud-lister/azure.test.ts +132 -0
  421. package/src/cloud-lister/azure.ts +142 -0
  422. package/src/cloud-lister/common.ts +115 -0
  423. package/src/cloud-lister/credentials.ts +110 -0
  424. package/src/cloud-lister/fakes.ts +66 -0
  425. package/src/cloud-lister/gcp.test.ts +121 -0
  426. package/src/cloud-lister/gcp.ts +159 -0
  427. package/src/cloud-lister/guard.test.ts +198 -0
  428. package/src/cloud-lister/guard.ts +111 -0
  429. package/src/cloud-lister/ids.ts +54 -0
  430. package/src/cloud-lister/index.ts +134 -0
  431. package/src/cloud-lister/kubernetes.test.ts +160 -0
  432. package/src/cloud-lister/kubernetes.ts +284 -0
  433. package/src/cloud-lister/markers.ts +74 -0
  434. package/src/cloud-lister/mask.ts +77 -0
  435. package/src/cloud-lister/shape.test.ts +81 -0
  436. package/src/cloud-lister/sigv4.ts +70 -0
  437. package/src/cloud-lister/transport.ts +118 -0
  438. package/src/cloud-lister/types.ts +135 -0
  439. package/src/components/capability-plugin-loader.ts +4 -0
  440. package/src/components/capability-plugin.ts +10 -0
  441. package/src/components/capability.ts +229 -0
  442. package/src/components/cli-support.ts +70 -14
  443. package/src/components/declared-reads.test.ts +103 -0
  444. package/src/components/declared-reads.ts +134 -0
  445. package/src/components/deploy-units.ts +4 -0
  446. package/src/components/driver.ts +89 -4
  447. package/src/components/fan-out-run.ts +95 -15
  448. package/src/components/fan-out-support.ts +10 -1
  449. package/src/components/fan-out.ts +30 -0
  450. package/src/components/groups.test.ts +320 -0
  451. package/src/components/groups.ts +880 -0
  452. package/src/components/index.ts +42 -0
  453. package/src/components/plan-shape-conformance.ts +130 -0
  454. package/src/components/pr-env-run-bundle.test.ts +2 -0
  455. package/src/components/pr-run.test.ts +516 -3
  456. package/src/components/pr-run.ts +231 -41
  457. package/src/components/promote-rounds.ts +25 -2
  458. package/src/components/promote.ts +55 -5
  459. package/src/components/registry.test.ts +1 -1
  460. package/src/components/release-gate.ts +115 -30
  461. package/src/components/release-groups.test.ts +362 -0
  462. package/src/components/run-groups.ts +113 -0
  463. package/src/components/starter-plugin.ts +6 -0
  464. package/src/components/verbs/index.ts +1 -0
  465. package/src/components/verbs/member-outputs.test.ts +205 -0
  466. package/src/components/verbs/member-outputs.ts +308 -0
  467. package/src/config.ts +44 -0
  468. package/src/deep-observation.ts +5 -23
  469. package/src/drift-record.test.ts +596 -0
  470. package/src/drift-record.ts +1206 -0
  471. package/src/engine-facts.guard.test.ts +111 -0
  472. package/src/engine-facts.test.ts +178 -0
  473. package/src/engine-facts.ts +154 -0
  474. package/src/gated-waves-bundle.test.ts +2 -0
  475. package/src/graph-ir-live.test.ts +92 -0
  476. package/src/graph-ir.ts +317 -7
  477. package/src/graph-refs.ts +416 -0
  478. package/src/graph-schema/fixtures/adopt.json +15 -0
  479. package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  480. package/src/graph-schema/fixtures/index.json +68 -0
  481. package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  482. package/src/graph-schema/fixtures.ts +267 -0
  483. package/src/graph-schema/generate.ts +263 -0
  484. package/src/graph-schema/graph-schema.test.ts +153 -0
  485. package/src/graph-schema/graph.schema.json +1387 -0
  486. package/src/health.schema.json +2 -1
  487. package/src/identity.ts +2 -0
  488. package/src/index.ts +2 -0
  489. package/src/instance-nodes.ts +288 -0
  490. package/src/lexicon.ts +76 -0
  491. package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
  492. package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
  493. package/src/lifecycle/adoption-file.test.ts +100 -0
  494. package/src/lifecycle/adoption-file.ts +154 -0
  495. package/src/lifecycle/cloud-index.test.ts +517 -0
  496. package/src/lifecycle/cloud-index.ts +736 -0
  497. package/src/lifecycle/derive-edges.test.ts +193 -0
  498. package/src/lifecycle/derive-edges.ts +182 -0
  499. package/src/lifecycle/git.ts +10 -4
  500. package/src/lifecycle/identity-match.test.ts +308 -0
  501. package/src/lifecycle/identity-match.ts +257 -0
  502. package/src/lifecycle/index-diff.test.ts +198 -0
  503. package/src/lifecycle/index-diff.ts +476 -0
  504. package/src/lifecycle/index-document.test.ts +64 -0
  505. package/src/lifecycle/index-document.ts +299 -0
  506. package/src/lifecycle/index-drift.test.ts +191 -0
  507. package/src/lifecycle/index-drift.ts +304 -0
  508. package/src/lifecycle/index-import.test.ts +255 -0
  509. package/src/lifecycle/index-import.ts +634 -0
  510. package/src/lifecycle/index-join.test.ts +250 -0
  511. package/src/lifecycle/index-join.ts +304 -0
  512. package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
  513. package/src/lifecycle/index.ts +4 -0
  514. package/src/lifecycle/member-ledger.test.ts +25 -7
  515. package/src/lifecycle/observation-baseline.test.ts +72 -1
  516. package/src/lifecycle/observation-baseline.ts +190 -27
  517. package/src/lifecycle/observe.ts +6 -1
  518. package/src/lifecycle/query.test.ts +69 -1
  519. package/src/lifecycle/query.ts +103 -10
  520. package/src/lifecycle/replay.test.ts +9 -5
  521. package/src/lifecycle/replay.ts +9 -5
  522. package/src/lifecycle/scenario.ts +4 -3
  523. package/src/lifecycle/snapshot-store-fake.ts +29 -0
  524. package/src/lifecycle/snapshot-store-s3.ts +316 -0
  525. package/src/lifecycle/snapshot-store.test.ts +385 -0
  526. package/src/lifecycle/snapshot-store.ts +981 -0
  527. package/src/lifecycle/snapshot.test.ts +13 -0
  528. package/src/lifecycle/snapshot.ts +23 -6
  529. package/src/lifecycle/type-listing.test.ts +166 -0
  530. package/src/lifecycle/type-listing.ts +309 -0
  531. package/src/lint/component-checks.ts +17 -1
  532. package/src/lint/config.test.ts +3 -0
  533. package/src/lint/presets/relaxed.json +2 -1
  534. package/src/lint/presets/strict.json +2 -1
  535. package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
  536. package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
  537. package/src/lint/rules/comp/comp.test.ts +2 -1
  538. package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
  539. package/src/lint/rules/comp/comp008.test.ts +77 -0
  540. package/src/lint/rules/comp/index.ts +4 -1
  541. package/src/observation-edges.test.ts +33 -0
  542. package/src/observation.ts +36 -1
  543. package/src/op/activities/activity-contracts.ts +1 -1
  544. package/src/op/activities/change-set.ts +8 -5
  545. package/src/op/activity-failure.ts +36 -0
  546. package/src/op/index.ts +2 -1
  547. package/src/op/local-executor.test.ts +80 -0
  548. package/src/op/local-executor.ts +27 -9
  549. package/src/pr-loop-bundle.test.ts +5 -2
  550. package/src/pr-loop.test.ts +43 -0
  551. package/src/pr-loop.ts +111 -2
  552. package/src/pulumi-urn.ts +151 -0
  553. package/src/release-floors.ts +22 -0
  554. package/src/sensitive-key.ts +34 -0
  555. package/src/workspace/adoption-report.test.ts +61 -0
  556. package/src/workspace/adoption-report.ts +103 -0
  557. package/src/workspace/capabilities-contract.test.ts +4 -0
  558. package/src/workspace/capabilities-contract.ts +9 -1
  559. package/src/workspace/capabilities.schema.json +98 -0
  560. package/src/workspace/change-set.schema.json +42 -3
  561. package/src/workspace/ci-cloud-identity.ts +31 -1
  562. package/src/workspace/compose-graph.test.ts +12 -0
  563. package/src/workspace/compose-graph.ts +12 -1
  564. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  565. package/src/workspace/conformance/index.ts +6 -1
  566. package/src/workspace/drift-cli.test.ts +65 -0
  567. package/src/workspace/drift-cli.ts +365 -0
  568. package/src/workspace/drift.schema.json +672 -0
  569. package/src/workspace/graph-cli.ts +5 -0
  570. package/src/workspace/graph.schema.json +72 -0
  571. package/src/workspace/init.test.ts +38 -1
  572. package/src/workspace/init.ts +50 -8
  573. package/src/workspace/intent-gaps.test.ts +18 -2
  574. package/src/workspace/ledger.schema.json +1 -1
  575. package/src/workspace/nested-graph.ts +1 -0
  576. package/src/workspace/pr-report.schema.json +51 -2
  577. package/src/workspace/promote-waves.test.ts +296 -0
  578. package/src/workspace/promote-waves.ts +340 -0
  579. package/src/workspace/promote.test.ts +158 -4
  580. package/src/workspace/promote.ts +398 -57
  581. package/src/workspace/reason-codes.test.ts +4 -1
  582. package/src/workspace/reason-codes.ts +14 -0
  583. package/src/workspace/search-contract.ts +2 -0
  584. package/src/workspace/search.schema.json +103 -5
package/src/graph-refs.ts CHANGED
@@ -36,8 +36,26 @@ export interface RefRule {
36
36
  match?: "id" | "arn" | "name" | "any";
37
37
  /** Constrain the target kind (disambiguates identifier collisions). */
38
38
  targetKind?: string;
39
+ /**
40
+ * The target kind when the rule runs over indexed nodes (#4522, ruled on
41
+ * INTENTIUS/behold#1023 Q4), for a rule that has no {@link targetKind}
42
+ * because its values are logical ids (`enrichLiveAttrs`), which are unique.
43
+ * Over an index the same path holds a physical value, often a name, so the
44
+ * target kind is what keeps it from joining whatever else has that name.
45
+ */
46
+ indexedTargetKind?: string;
39
47
  /** `reference` → an edge; `containment` → a boundary hint (#779), not an edge. */
40
48
  relation: "reference" | "containment";
49
+ /**
50
+ * The path holds a reference only sometimes (#4535): an env var's value, a
51
+ * selector's label, a binding subject that may be a user. A value that
52
+ * names no node of the rule's kind is then not reported as dangling, since
53
+ * most such values are not references at all, and a path read by several
54
+ * rules (one per target kind) would otherwise report every value matched by
55
+ * one rule as dangling under the others. A match is still a typed, scoped
56
+ * match, and two candidates still draw no edge.
57
+ */
58
+ optional?: boolean;
41
59
  /** Edge / containment label (e.g. "in VPC", "sg", "targets"). */
42
60
  label?: string;
43
61
  /**
@@ -61,10 +79,29 @@ export interface RefRule {
61
79
  viaAttr?: string;
62
80
  }
63
81
 
82
+ /**
83
+ * A typed tag rule (#4522, ruled on INTENTIUS/behold#1023 Q5): a tag whose
84
+ * key names a cloud id, read off an indexed node's `tags`. Tags make edges
85
+ * only through these, never by matching equal tag values, because two
86
+ * objects can carry the same value for any reason.
87
+ */
88
+ export interface TagRefRule {
89
+ /** Holder kind; absent for every kind of this lexicon. */
90
+ from?: string;
91
+ /** The exact tag key, or a key prefix ending in `/` whose remainder is the value. */
92
+ key: string;
93
+ /** `value`: the tag's value is the id. `key-suffix`: the key after {@link key} is (`kubernetes.io/cluster/<name>`). */
94
+ read: "value" | "key-suffix";
95
+ targetKind: string;
96
+ label?: string;
97
+ }
98
+
64
99
  /** A lexicon's reference knowledge — its identity map and reference rules. */
65
100
  export interface ReferenceCatalog {
66
101
  identities: IdentityRule[];
67
102
  refs: RefRule[];
103
+ /** Typed tag rules, used over indexed nodes only (#4522). */
104
+ tagRefs?: TagRefRule[];
68
105
  }
69
106
 
70
107
  /** `child` is contained by `parent` (subnet ∈ VPC). For #779's boundary boxes. */
@@ -193,8 +230,12 @@ export function reconstructEdges(nodes: IRNode[], catalog: ReferenceCatalog): Re
193
230
  if (rule.from !== node.kind) continue;
194
231
  for (const value of readPath(node.attrs, rule.path)) {
195
232
  const candidates = index.get(value) ?? [];
233
+ // `chant graph --live` keeps its first-candidate pick: its values are
234
+ // logical ids and physical ids, which name one node. An index refuses
235
+ // the pick instead (reconstructIndexedEdges, #4522).
196
236
  const match = rule.targetKind ? candidates.find((c) => c.kind === rule.targetKind) : candidates[0];
197
237
  if (!match) {
238
+ if (rule.optional) continue;
198
239
  dangling.push({ from: node.id, path: rule.path, value, ...(rule.targetKind ? { targetKind: rule.targetKind } : {}) });
199
240
  continue;
200
241
  }
@@ -261,3 +302,378 @@ export function containmentGroups(pairs: ContainmentPair[]): Record<string, stri
261
302
  for (const parent of [...byParent.keys()].sort()) out[parent] = [...byParent.get(parent)!].sort();
262
303
  return out;
263
304
  }
305
+
306
+ // ── Indexed nodes (#4522) ────────────────────────────────────────────────────
307
+
308
+ /** One lexicon's catalog, named, so a derived edge can say which rule made it. */
309
+ export interface NamedCatalog {
310
+ lexicon: string;
311
+ catalog: ReferenceCatalog;
312
+ }
313
+
314
+ /**
315
+ * Where an indexed node's identifiers are unique (INTENTIUS/behold#1023 Q2):
316
+ * its substrate and account, subscription, project or cluster, plus its
317
+ * region for a regional type. A global type (IAM, Route 53, CloudFront) has
318
+ * no region, and is matched by account only.
319
+ */
320
+ export interface IndexedNodeScope {
321
+ /** `<substrate>/<scopeId>`. */
322
+ account: string;
323
+ /** The region or location; undefined for a global type. */
324
+ region?: string;
325
+ }
326
+
327
+ /** A reference whose value matched several nodes: no edge is drawn (behold#1023 Q3). */
328
+ export interface AmbiguousRef {
329
+ from: string;
330
+ rule: string;
331
+ path: string;
332
+ value: string;
333
+ /** Every node the value matched, sorted. */
334
+ candidates: string[];
335
+ }
336
+
337
+ /** A reference whose target is not in the index, with the rule that read it. */
338
+ export interface IndexedDanglingRef extends DanglingRef {
339
+ rule: string;
340
+ }
341
+
342
+ export interface IndexedEdges {
343
+ /** Reference and containment edges, each `origin: "derived"` with its `rule`. */
344
+ edges: IREdge[];
345
+ containment: ContainmentPair[];
346
+ dangling: IndexedDanglingRef[];
347
+ ambiguous: AmbiguousRef[];
348
+ }
349
+
350
+ /** Values a masking or normalization pass wrote: never an identifier. */
351
+ const PLACEHOLDER_VALUES: ReadonlySet<string> = new Set(["[REDACTED]", "<chant:unresolved>", "<masked>"]);
352
+
353
+ /** The attribute a rule reads through, as a declared graph's `viaAttr` names it: `SecurityGroupIds[]` is `SecurityGroupIds`. */
354
+ function topAttr(path: string): string {
355
+ const first = path.split(".")[0];
356
+ return first.endsWith("[]") ? first.slice(0, -2) : first;
357
+ }
358
+
359
+ /** `<lexicon>:<from kind>:<path>`, the id a derived edge carries. */
360
+ export function refRuleId(lexicon: string, from: string, path: string): string {
361
+ return `${lexicon}:${from}:${path}`;
362
+ }
363
+
364
+ const byString = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0);
365
+
366
+ /**
367
+ * Derive edges over indexed nodes (#4522) with each lexicon's catalog, under
368
+ * the rules ruled on INTENTIUS/behold#1023:
369
+ *
370
+ * - the join key is the scoped value: a reference on a node in region R
371
+ * matches nodes of the same account in R, and global nodes of the same
372
+ * account; a reference on a global node matches global nodes only;
373
+ * - a rule's target kind is `targetKind`, else `indexedTargetKind`;
374
+ * - a value that matches more than one node draws no edge and is returned in
375
+ * `ambiguous` with every candidate, in place of {@link reconstructEdges}'
376
+ * first-candidate pick, so two reads of one account in another node order
377
+ * give the same edges;
378
+ * - tags make edges only through a catalog's typed `tagRefs`.
379
+ *
380
+ * A node is matched by its id, physical id, native id, cloud ids, and the
381
+ * catalog's identity paths. Rules apply to nodes of their own lexicon. Pure.
382
+ */
383
+ export function reconstructIndexedEdges(
384
+ nodes: readonly IRNode[],
385
+ catalogs: readonly NamedCatalog[],
386
+ scopeOf: (node: IRNode) => IndexedNodeScope,
387
+ ): IndexedEdges {
388
+ const sorted = [...nodes].sort((a, b) => byString(a.id, b.id));
389
+ const keyOf = (scope: IndexedNodeScope, value: string) => `${scope.account}\u0000${scope.region ?? ""}\u0000${value}`;
390
+ const index = new Map<string, Map<string, string>>();
391
+ const add = (scope: IndexedNodeScope, value: string | undefined, node: IRNode): void => {
392
+ if (value === undefined || value === "" || PLACEHOLDER_VALUES.has(value)) return;
393
+ const k = keyOf(scope, value);
394
+ (index.get(k) ?? index.set(k, new Map()).get(k)!).set(node.id, node.kind);
395
+ };
396
+ const identities = catalogs.flatMap((c) => c.catalog.identities);
397
+ for (const node of sorted) {
398
+ const scope = scopeOf(node);
399
+ add(scope, node.id, node);
400
+ add(scope, node.physicalId, node);
401
+ add(scope, node.nativeId, node);
402
+ for (const c of node.facts?.cloudIds ?? []) add(scope, c.value, node);
403
+ for (const rule of identities) {
404
+ if (rule.kind !== node.kind) continue;
405
+ for (const p of rule.ids) for (const v of readPath(node.attrs, p)) add(scope, v, node);
406
+ }
407
+ }
408
+
409
+ const lookup = (holder: IRNode, value: string): Map<string, string> => {
410
+ const scope = scopeOf(holder);
411
+ const out = new Map<string, string>();
412
+ const keys = scope.region !== undefined ? [keyOf(scope, value), keyOf({ account: scope.account }, value)] : [keyOf(scope, value)];
413
+ for (const k of keys) for (const [id, kind] of index.get(k) ?? []) out.set(id, kind);
414
+ return out;
415
+ };
416
+
417
+ const edges = new Map<string, IREdge>();
418
+ const containment = new Map<string, ContainmentPair>();
419
+ // Keyed by holder, rule and value: two rules with one id (the describe-shape
420
+ // and template-shape rules for the same path) report a reference once.
421
+ const dangling = new Map<string, IndexedDanglingRef>();
422
+ const ambiguous = new Map<string, AmbiguousRef>();
423
+
424
+ const resolve = (
425
+ holder: IRNode,
426
+ value: string,
427
+ rule: { id: string; path: string; targetKind?: string; relation: "reference" | "containment"; via: string; label?: string; optional?: boolean },
428
+ ): void => {
429
+ if (PLACEHOLDER_VALUES.has(value)) return;
430
+ const found = lookup(holder, value);
431
+ let candidates = [...found.entries()].filter(([, kind]) => rule.targetKind === undefined || kind === rule.targetKind).map(([id]) => id);
432
+ if (candidates.includes(holder.id)) {
433
+ if (candidates.length === 1) return; // a self-reference (an SG rule to its own group)
434
+ candidates = candidates.filter((id) => id !== holder.id);
435
+ }
436
+ candidates.sort(byString);
437
+ if (candidates.length === 0) {
438
+ if (rule.optional) return;
439
+ dangling.set(`${holder.id}|${rule.id}|${value}`, { from: holder.id, path: rule.path, value, rule: rule.id, ...(rule.targetKind ? { targetKind: rule.targetKind } : {}) });
440
+ return;
441
+ }
442
+ if (candidates.length > 1) {
443
+ ambiguous.set(`${holder.id}|${rule.id}|${value}`, { from: holder.id, rule: rule.id, path: rule.path, value, candidates });
444
+ return;
445
+ }
446
+ const to = candidates[0];
447
+ const key = `${holder.id}|${to}|${rule.via}|${rule.relation}`;
448
+ const prev = edges.get(key);
449
+ // Two rules can draw one edge (the describe-shape and template-shape rules
450
+ // read the same attribute); the smaller rule id is kept, so the result
451
+ // does not depend on catalog order.
452
+ if (!prev || byString(rule.id, prev.rule!) < 0) {
453
+ edges.set(key, {
454
+ from: holder.id,
455
+ to,
456
+ kind: "ref",
457
+ viaAttr: rule.via,
458
+ grain: "property",
459
+ origin: "derived",
460
+ rule: rule.id,
461
+ relation: rule.relation,
462
+ });
463
+ }
464
+ if (rule.relation === "containment") {
465
+ const ck = `${holder.id}|${to}`;
466
+ const prevPair = containment.get(ck);
467
+ if (!prevPair || (rule.label !== undefined && (prevPair.label === undefined || byString(rule.label, prevPair.label) < 0))) {
468
+ containment.set(ck, { child: holder.id, parent: to, ...(rule.label ? { label: rule.label } : {}) });
469
+ }
470
+ }
471
+ };
472
+
473
+ for (const node of sorted) {
474
+ for (const { lexicon, catalog } of catalogs) {
475
+ if (node.lexicon !== lexicon) continue;
476
+ for (const rule of catalog.refs) {
477
+ if (rule.from !== node.kind) continue;
478
+ const spec = {
479
+ id: refRuleId(lexicon, rule.from, rule.path),
480
+ path: rule.path,
481
+ targetKind: rule.targetKind ?? rule.indexedTargetKind,
482
+ relation: rule.relation,
483
+ via: rule.viaAttr ?? topAttr(rule.path),
484
+ ...(rule.label ? { label: rule.label } : {}),
485
+ ...(rule.optional ? { optional: true } : {}),
486
+ };
487
+ for (const value of new Set(readPath(node.attrs, rule.path))) resolve(node, value, spec);
488
+ }
489
+ for (const rule of catalog.tagRefs ?? []) {
490
+ if (rule.from !== undefined && rule.from !== node.kind) continue;
491
+ const tags = node.tags ?? {};
492
+ const spec = {
493
+ id: refRuleId(lexicon, rule.from ?? node.kind, `tag:${rule.key}`),
494
+ path: `tag:${rule.key}`,
495
+ targetKind: rule.targetKind,
496
+ relation: "reference" as const,
497
+ via: "Tags",
498
+ ...(rule.label ? { label: rule.label } : {}),
499
+ };
500
+ if (rule.read === "value") {
501
+ const v = tags[rule.key];
502
+ if (typeof v === "string" && v !== "") resolve(node, v, spec);
503
+ } else {
504
+ for (const k of Object.keys(tags).sort()) {
505
+ if (k.startsWith(rule.key) && k.length > rule.key.length) resolve(node, k.slice(rule.key.length), spec);
506
+ }
507
+ }
508
+ }
509
+ }
510
+ }
511
+
512
+ const edgeKey = (e: IREdge) => `${e.from}|${e.to}|${e.viaAttr ?? ""}|${e.relation ?? ""}`;
513
+ return {
514
+ edges: [...edges.values()].sort((a, b) => byString(edgeKey(a), edgeKey(b))),
515
+ containment: [...containment.values()].sort((a, b) => byString(`${a.child}|${a.parent}`, `${b.child}|${b.parent}`)),
516
+ dangling: [...dangling.values()].sort((a, b) => byString(`${a.from}|${a.rule}|${a.value}`, `${b.from}|${b.rule}|${b.value}`)),
517
+ ambiguous: [...ambiguous.values()].sort((a, b) => byString(`${a.from}|${a.rule}|${a.value}`, `${b.from}|${b.rule}|${b.value}`)),
518
+ };
519
+ }
520
+
521
+ // ── Identity matches over indexed nodes (#4523) ──────────────────────────────
522
+
523
+ /** One value that names an indexed node: its native id, or one of its cloud ids. */
524
+ export interface NodeIdentityValue {
525
+ /** `native-id`, or the cloud id's kind (`arn`, `provider-id`, ...). */
526
+ kind: string;
527
+ value: string;
528
+ }
529
+
530
+ /** The lexicon whose nodes can be identity-matched, and the values that name each one. */
531
+ export interface IdentityOwners {
532
+ lexicon: string;
533
+ /** The values that name a node of this lexicon. Absent: its native id and its ARN cloud ids. */
534
+ identities?(node: IRNode): NodeIdentityValue[];
535
+ }
536
+
537
+ /** A value that named several nodes: no identity-match edge is drawn (behold#1023 Q3). */
538
+ export interface AmbiguousIdentityRef {
539
+ from: string;
540
+ path: string;
541
+ value: string;
542
+ /** Every node the value named, sorted. */
543
+ candidates: string[];
544
+ }
545
+
546
+ export interface IdentityEdges {
547
+ /** Each `origin: "derived"`, `rule: "identity-match:..."`. */
548
+ edges: IREdge[];
549
+ ambiguous: AmbiguousIdentityRef[];
550
+ }
551
+
552
+ /** The label every edge of the identity pass carries, as the prefix of its `rule`. */
553
+ export const IDENTITY_MATCH_RULE = "identity-match";
554
+
555
+ /** `identity-match:<owner lexicon>:<owner kind>:<identity kind>`. */
556
+ export function identityMatchRuleId(lexicon: string, kind: string, identityKind: string): string {
557
+ return `${IDENTITY_MATCH_RULE}:${lexicon}:${kind}:${identityKind}`;
558
+ }
559
+
560
+ /** The default identities of an indexed node: the native id, and any ARN among its cloud ids. */
561
+ export function defaultNodeIdentities(node: IRNode): NodeIdentityValue[] {
562
+ const out: NodeIdentityValue[] = [];
563
+ if (node.nativeId) out.push({ kind: "native-id", value: node.nativeId });
564
+ for (const c of node.facts?.cloudIds ?? []) if (c.kind === "arn") out.push({ kind: "arn", value: c.value });
565
+ return out;
566
+ }
567
+
568
+ /** Top-level keys whose values the identity pass never reads: tags make edges only through typed rules (behold#1023 Q5). */
569
+ const IDENTITY_SKIPPED_KEYS: ReadonlySet<string> = new Set(["Tags", "tags", "TagSet", "labels", "Labels"]);
570
+
571
+ /** Short values are names anything can carry (`default`, `app`); whitespace means prose, not an identifier. */
572
+ const MIN_IDENTITY_VALUE = 6;
573
+ const MAX_IDENTITY_VALUE = 2048;
574
+
575
+ function identityCandidateValue(v: string): boolean {
576
+ return v.length >= MIN_IDENTITY_VALUE && v.length <= MAX_IDENTITY_VALUE && !/\s/.test(v) && !PLACEHOLDER_VALUES.has(v);
577
+ }
578
+
579
+ /** Every string leaf under `attrs` with its path (`a.b`, `arr[].c`), skipping tag subtrees. */
580
+ function stringLeaves(attrs: Record<string, unknown>): Array<{ path: string; value: string }> {
581
+ const out: Array<{ path: string; value: string }> = [];
582
+ const walk = (v: unknown, path: string): void => {
583
+ if (typeof v === "string") {
584
+ out.push({ path, value: v });
585
+ } else if (Array.isArray(v)) {
586
+ for (const x of v) walk(x, `${path}[]`);
587
+ } else if (v && typeof v === "object") {
588
+ for (const k of Object.keys(v as Record<string, unknown>).sort()) walk((v as Record<string, unknown>)[k], path ? `${path}.${k}` : k);
589
+ }
590
+ };
591
+ for (const k of Object.keys(attrs).sort()) {
592
+ if (IDENTITY_SKIPPED_KEYS.has(k)) continue;
593
+ walk(attrs[k], k);
594
+ }
595
+ return out;
596
+ }
597
+
598
+ /**
599
+ * The identity-match pass (#4523, ruled on INTENTIUS/behold#1023 Q1-Q3): the
600
+ * second pass over indexed nodes, after {@link reconstructIndexedEdges}. A
601
+ * string attribute on one node equal to a value that names another node (its
602
+ * native id, which for aws is the CloudFormation `primaryIdentifier`, or its
603
+ * ARN) draws an edge labelled `identity-match`:
604
+ *
605
+ * - only nodes of a lexicon in `owners` are named; holders are any node;
606
+ * - the join key is the scoped value, as in the catalog pass: a holder in
607
+ * region R reaches nodes of its account in R and global nodes of its
608
+ * account; a global holder reaches global nodes only;
609
+ * - a value naming more than one node draws no edge and is returned in
610
+ * `ambiguous` with every candidate;
611
+ * - tag subtrees are never read, values under {@link MIN_IDENTITY_VALUE}
612
+ * characters or with whitespace are skipped, and a value naming the holder
613
+ * itself is not a reference;
614
+ * - a pair the catalog pass already joined (`existing`) gets no second edge.
615
+ *
616
+ * Pure, and independent of node order.
617
+ */
618
+ export function reconstructIdentityEdges(
619
+ nodes: readonly IRNode[],
620
+ owners: readonly IdentityOwners[],
621
+ scopeOf: (node: IRNode) => IndexedNodeScope,
622
+ existing: readonly IREdge[] = [],
623
+ ): IdentityEdges {
624
+ const sorted = [...nodes].sort((a, b) => byString(a.id, b.id));
625
+ const ownerBy = new Map(owners.map((o) => [o.lexicon, o]));
626
+ const keyOf = (scope: IndexedNodeScope, value: string) => `${scope.account}\u0000${scope.region ?? ""}\u0000${value}`;
627
+ // key -> node id -> the rule that names it
628
+ const index = new Map<string, Map<string, string>>();
629
+ const own = new Map<string, Set<string>>();
630
+ for (const node of sorted) {
631
+ const owner = ownerBy.get(node.lexicon);
632
+ if (!owner || node.unmappedKind) continue;
633
+ const scope = scopeOf(node);
634
+ const values = (owner.identities ?? defaultNodeIdentities)(node);
635
+ own.set(node.id, new Set(values.map((v) => v.value)));
636
+ for (const { kind, value } of values) {
637
+ if (!identityCandidateValue(value)) continue;
638
+ const k = keyOf(scope, value);
639
+ const m = index.get(k) ?? index.set(k, new Map()).get(k)!;
640
+ const rule = identityMatchRuleId(owner.lexicon, node.kind, kind);
641
+ const prev = m.get(node.id);
642
+ if (prev === undefined || byString(rule, prev) < 0) m.set(node.id, rule);
643
+ }
644
+ }
645
+
646
+ const joined = new Set(existing.map((e) => `${e.from}|${e.to}`));
647
+ const edges = new Map<string, IREdge>();
648
+ const ambiguous = new Map<string, AmbiguousIdentityRef>();
649
+
650
+ for (const holder of sorted) {
651
+ const scope = scopeOf(holder);
652
+ const keys = scope.region !== undefined ? (v: string) => [keyOf(scope, v), keyOf({ account: scope.account }, v)] : (v: string) => [keyOf(scope, v)];
653
+ const mine = own.get(holder.id) ?? new Set<string>([holder.nativeId ?? "", holder.physicalId ?? ""]);
654
+ for (const { path, value } of stringLeaves(holder.attrs ?? {})) {
655
+ if (!identityCandidateValue(value) || mine.has(value)) continue;
656
+ const found = new Map<string, string>();
657
+ for (const k of keys(value)) for (const [id, rule] of index.get(k) ?? []) if (id !== holder.id) found.set(id, rule);
658
+ if (found.size === 0) continue;
659
+ if (found.size > 1) {
660
+ ambiguous.set(`${holder.id}|${path}|${value}`, { from: holder.id, path, value, candidates: [...found.keys()].sort(byString) });
661
+ continue;
662
+ }
663
+ const [[to, rule]] = [...found.entries()];
664
+ if (joined.has(`${holder.id}|${to}`)) continue;
665
+ const via = topAttr(path);
666
+ const key = `${holder.id}|${to}|${via}`;
667
+ const prev = edges.get(key);
668
+ if (!prev || byString(rule, prev.rule!) < 0) {
669
+ edges.set(key, { from: holder.id, to, kind: "ref", viaAttr: via, grain: "property", origin: "derived", rule, relation: "reference" });
670
+ }
671
+ }
672
+ }
673
+
674
+ const edgeKey = (e: IREdge) => `${e.from}|${e.to}|${e.viaAttr ?? ""}`;
675
+ return {
676
+ edges: [...edges.values()].sort((a, b) => byString(edgeKey(a), edgeKey(b))),
677
+ ambiguous: [...ambiguous.values()].sort((a, b) => byString(`${a.from}|${a.path}|${a.value}`, `${b.from}|${b.path}|${b.value}`)),
678
+ };
679
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "adopt": "v1",
3
+ "index": {
4
+ "scopeKey": "cloud/aws/000000000000/9bd41033bb42",
5
+ "version": "20261026T120000Z-e2f92b265a4d.json",
6
+ "digest": "e2f92b265a4dd604e013b3f55eafe5cf317ea1f5b7c54b4c5187120d0dcfe10d"
7
+ },
8
+ "select": [
9
+ {
10
+ "node": "aws/000000000000/us-east-1/AWS::S3::Bucket/web-logs",
11
+ "as": "aws_s3_bucket.web_logs",
12
+ "type": "aws_s3_bucket"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,169 @@
1
+ {
2
+ "version": 1,
3
+ "meta": {
4
+ "engineGraph": {
5
+ "format": 1,
6
+ "engine": {
7
+ "name": "choudoufu",
8
+ "version": "0.33.0"
9
+ },
10
+ "scope": {
11
+ "kind": "estate",
12
+ "estate": "web"
13
+ },
14
+ "observedAt": "2026-10-26T12:00:00Z",
15
+ "markerScheme": {
16
+ "ownerKey": "tofu-estate",
17
+ "addressKey": "tofu-address",
18
+ "continuationKeys": [
19
+ "tofu-slot"
20
+ ],
21
+ "encoding": {
22
+ "tags": "escaped",
23
+ "labels": "escaped"
24
+ }
25
+ },
26
+ "gaps": [
27
+ {
28
+ "address": "aws_iam_role.legacy",
29
+ "reason": "unsupported-kind",
30
+ "detail": "no lister for this type on this provider"
31
+ }
32
+ ]
33
+ }
34
+ },
35
+ "nodes": [
36
+ {
37
+ "id": "aws_s3_bucket.logs",
38
+ "kind": "aws_s3_bucket",
39
+ "lexicon": "terraform",
40
+ "attrs": {
41
+ "address": "aws_s3_bucket.logs"
42
+ },
43
+ "physicalId": "web-logs",
44
+ "facts": {
45
+ "ownership": "bound",
46
+ "ownedBy": "record",
47
+ "cloudIds": [
48
+ {
49
+ "kind": "import-id",
50
+ "value": "web-logs"
51
+ },
52
+ {
53
+ "kind": "arn",
54
+ "value": "arn:aws:s3:::web-logs"
55
+ }
56
+ ],
57
+ "recordVersions": {
58
+ "kept": true,
59
+ "versions": [
60
+ {
61
+ "id": "3sL4kqtJlcpXroDTDmJ.rmSpXd3dIbrHY",
62
+ "writtenAt": "2026-10-26T11:58:02Z",
63
+ "current": true,
64
+ "deleted": false
65
+ },
66
+ {
67
+ "id": "2bE8x0l1BqzDmoPTmD7M7bGqFrsTFTQS",
68
+ "writtenAt": "2026-10-20T09:14:40Z",
69
+ "current": false,
70
+ "deleted": false
71
+ }
72
+ ]
73
+ },
74
+ "observedAt": "2026-10-26T12:00:00Z"
75
+ }
76
+ },
77
+ {
78
+ "id": "module.app.aws_eip.pool",
79
+ "kind": "aws_eip",
80
+ "lexicon": "terraform",
81
+ "attrs": {
82
+ "address": "module.app.aws_eip.pool"
83
+ }
84
+ },
85
+ {
86
+ "id": "module.app.aws_instance.web",
87
+ "kind": "aws_instance",
88
+ "lexicon": "terraform",
89
+ "attrs": {
90
+ "address": "module.app.aws_instance.web"
91
+ }
92
+ },
93
+ {
94
+ "id": "module.app[0].aws_eip.pool[1]",
95
+ "kind": "aws_eip",
96
+ "lexicon": "terraform",
97
+ "attrs": {
98
+ "address": "module.app[0].aws_eip.pool[1]"
99
+ },
100
+ "physicalId": "eipalloc-0abc",
101
+ "instanceOf": "module.app.aws_eip.pool",
102
+ "instanceKey": "[1]",
103
+ "instanceScope": "module.app[0]",
104
+ "facts": {
105
+ "ownership": "bound",
106
+ "ownedBy": "marker",
107
+ "markers": {
108
+ "owner": "web",
109
+ "address": "module.app:0.aws_eip.pool:1",
110
+ "encoding": "escaped",
111
+ "source": "live"
112
+ },
113
+ "cloudIds": [
114
+ {
115
+ "kind": "import-id",
116
+ "value": "eipalloc-0abc"
117
+ }
118
+ ],
119
+ "observedAt": "2026-10-26T12:00:00Z"
120
+ }
121
+ },
122
+ {
123
+ "id": "aws_sqs_queue.jobs",
124
+ "kind": "aws_sqs_queue",
125
+ "lexicon": "terraform",
126
+ "attrs": {
127
+ "address": "aws_sqs_queue.jobs"
128
+ },
129
+ "facts": {
130
+ "ownership": "foreign",
131
+ "markers": {
132
+ "owner": "billing",
133
+ "encoding": "escaped",
134
+ "source": "live"
135
+ },
136
+ "observedAt": "2026-10-26T12:00:00Z"
137
+ }
138
+ },
139
+ {
140
+ "id": "aws_sns_topic.alerts",
141
+ "kind": "aws_sns_topic",
142
+ "lexicon": "terraform",
143
+ "attrs": {
144
+ "address": "aws_sns_topic.alerts"
145
+ },
146
+ "facts": {
147
+ "ownership": "pending",
148
+ "observedAt": "2026-10-26T12:00:00Z"
149
+ }
150
+ }
151
+ ],
152
+ "edges": [
153
+ {
154
+ "from": "module.app[0].aws_eip.pool[1]",
155
+ "to": "module.app.aws_instance.web",
156
+ "kind": "ref",
157
+ "viaAttr": "dependencies",
158
+ "grain": "block"
159
+ },
160
+ {
161
+ "from": "module.app.aws_instance.web",
162
+ "to": "aws_s3_bucket.logs",
163
+ "kind": "ref",
164
+ "viaAttr": "dependencies",
165
+ "grain": "block"
166
+ }
167
+ ],
168
+ "groups": {}
169
+ }