@intentius/chant 0.124.51 → 0.124.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (593) hide show
  1. package/dist/audit/discover.d.ts.map +1 -1
  2. package/dist/audit/discover.js +3 -1
  3. package/dist/behaviour.d.ts +1 -1
  4. package/dist/behaviour.d.ts.map +1 -1
  5. package/dist/capabilities/templates.d.ts +65 -0
  6. package/dist/capabilities/templates.d.ts.map +1 -0
  7. package/dist/capabilities/templates.js +193 -0
  8. package/dist/capabilities/templates.json +145 -0
  9. package/dist/change-set-graph.d.ts +35 -3
  10. package/dist/change-set-graph.d.ts.map +1 -1
  11. package/dist/change-set-graph.js +60 -9
  12. package/dist/change-set.d.ts +65 -7
  13. package/dist/change-set.d.ts.map +1 -1
  14. package/dist/change-set.js +23 -12
  15. package/dist/ci-identity.d.ts +7 -2
  16. package/dist/ci-identity.d.ts.map +1 -1
  17. package/dist/ci-identity.js +4 -1
  18. package/dist/cli/commands/import-index.d.ts +52 -0
  19. package/dist/cli/commands/import-index.d.ts.map +1 -0
  20. package/dist/cli/commands/import-index.js +226 -0
  21. package/dist/cli/commands/lint.d.ts.map +1 -1
  22. package/dist/cli/commands/lint.js +4 -2
  23. package/dist/cli/handlers/components.d.ts.map +1 -1
  24. package/dist/cli/handlers/components.js +11 -2
  25. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  26. package/dist/cli/handlers/fan-out.js +16 -3
  27. package/dist/cli/handlers/graph.d.ts.map +1 -1
  28. package/dist/cli/handlers/graph.js +22 -4
  29. package/dist/cli/handlers/index-cloud.d.ts +14 -0
  30. package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
  31. package/dist/cli/handlers/index-cloud.js +22 -0
  32. package/dist/cli/handlers/index-diff.d.ts +18 -0
  33. package/dist/cli/handlers/index-diff.d.ts.map +1 -0
  34. package/dist/cli/handlers/index-diff.js +142 -0
  35. package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
  36. package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
  37. package/dist/cli/handlers/lifecycle-drift.js +64 -0
  38. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  39. package/dist/cli/handlers/lifecycle.js +188 -28
  40. package/dist/cli/handlers/misc.d.ts +9 -0
  41. package/dist/cli/handlers/misc.d.ts.map +1 -1
  42. package/dist/cli/handlers/misc.js +77 -0
  43. package/dist/cli/handlers/pr.d.ts.map +1 -1
  44. package/dist/cli/handlers/pr.js +17 -3
  45. package/dist/cli/handlers/promote.d.ts.map +1 -1
  46. package/dist/cli/handlers/promote.js +89 -15
  47. package/dist/cli/handlers/query.d.ts.map +1 -1
  48. package/dist/cli/handlers/query.js +28 -4
  49. package/dist/cli/handlers/scenario.d.ts.map +1 -1
  50. package/dist/cli/handlers/scenario.js +8 -7
  51. package/dist/cli/handlers/search.d.ts +34 -4
  52. package/dist/cli/handlers/search.d.ts.map +1 -1
  53. package/dist/cli/handlers/search.js +216 -36
  54. package/dist/cli/main.d.ts.map +1 -1
  55. package/dist/cli/main.js +55 -1
  56. package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
  57. package/dist/cli/mcp/lifecycle-tools.js +3 -2
  58. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  59. package/dist/cli/mcp/resource-handlers.js +4 -3
  60. package/dist/cli/mcp/workspace-tools.js +1 -1
  61. package/dist/cli/parse-args.d.ts.map +1 -1
  62. package/dist/cli/parse-args.js +55 -2
  63. package/dist/cli/registry.d.ts +19 -1
  64. package/dist/cli/registry.d.ts.map +1 -1
  65. package/dist/cloud-lister/aws.d.ts +35 -0
  66. package/dist/cloud-lister/aws.d.ts.map +1 -0
  67. package/dist/cloud-lister/aws.js +203 -0
  68. package/dist/cloud-lister/azure.d.ts +34 -0
  69. package/dist/cloud-lister/azure.d.ts.map +1 -0
  70. package/dist/cloud-lister/azure.js +129 -0
  71. package/dist/cloud-lister/common.d.ts +40 -0
  72. package/dist/cloud-lister/common.d.ts.map +1 -0
  73. package/dist/cloud-lister/common.js +83 -0
  74. package/dist/cloud-lister/credentials.d.ts +50 -0
  75. package/dist/cloud-lister/credentials.d.ts.map +1 -0
  76. package/dist/cloud-lister/credentials.js +86 -0
  77. package/dist/cloud-lister/fakes.d.ts +27 -0
  78. package/dist/cloud-lister/fakes.d.ts.map +1 -0
  79. package/dist/cloud-lister/fakes.js +50 -0
  80. package/dist/cloud-lister/gcp.d.ts +42 -0
  81. package/dist/cloud-lister/gcp.d.ts.map +1 -0
  82. package/dist/cloud-lister/gcp.js +143 -0
  83. package/dist/cloud-lister/guard.d.ts +49 -0
  84. package/dist/cloud-lister/guard.d.ts.map +1 -0
  85. package/dist/cloud-lister/guard.js +103 -0
  86. package/dist/cloud-lister/ids.d.ts +23 -0
  87. package/dist/cloud-lister/ids.d.ts.map +1 -0
  88. package/dist/cloud-lister/ids.js +42 -0
  89. package/dist/cloud-lister/index.d.ts +67 -0
  90. package/dist/cloud-lister/index.d.ts.map +1 -0
  91. package/dist/cloud-lister/index.js +102 -0
  92. package/dist/cloud-lister/kubernetes.d.ts +64 -0
  93. package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
  94. package/dist/cloud-lister/kubernetes.js +276 -0
  95. package/dist/cloud-lister/markers.d.ts +31 -0
  96. package/dist/cloud-lister/markers.d.ts.map +1 -0
  97. package/dist/cloud-lister/markers.js +73 -0
  98. package/dist/cloud-lister/mask.d.ts +31 -0
  99. package/dist/cloud-lister/mask.d.ts.map +1 -0
  100. package/dist/cloud-lister/mask.js +74 -0
  101. package/dist/cloud-lister/sigv4.d.ts +29 -0
  102. package/dist/cloud-lister/sigv4.d.ts.map +1 -0
  103. package/dist/cloud-lister/sigv4.js +54 -0
  104. package/dist/cloud-lister/transport.d.ts +55 -0
  105. package/dist/cloud-lister/transport.d.ts.map +1 -0
  106. package/dist/cloud-lister/transport.js +86 -0
  107. package/dist/cloud-lister/types.d.ts +121 -0
  108. package/dist/cloud-lister/types.d.ts.map +1 -0
  109. package/dist/cloud-lister/types.js +24 -0
  110. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  111. package/dist/components/capability-plugin-loader.js +6 -0
  112. package/dist/components/capability-plugin.d.ts +9 -0
  113. package/dist/components/capability-plugin.d.ts.map +1 -1
  114. package/dist/components/capability.d.ts +224 -0
  115. package/dist/components/capability.d.ts.map +1 -1
  116. package/dist/components/cli-support.d.ts +21 -0
  117. package/dist/components/cli-support.d.ts.map +1 -1
  118. package/dist/components/cli-support.js +58 -14
  119. package/dist/components/covered-round.d.ts +173 -0
  120. package/dist/components/covered-round.d.ts.map +1 -0
  121. package/dist/components/covered-round.js +200 -0
  122. package/dist/components/declared-reads.d.ts +38 -0
  123. package/dist/components/declared-reads.d.ts.map +1 -0
  124. package/dist/components/declared-reads.js +131 -0
  125. package/dist/components/deploy-units.d.ts.map +1 -1
  126. package/dist/components/deploy-units.js +4 -0
  127. package/dist/components/driver.d.ts +42 -1
  128. package/dist/components/driver.d.ts.map +1 -1
  129. package/dist/components/driver.js +74 -3
  130. package/dist/components/fan-out-run.d.ts +23 -1
  131. package/dist/components/fan-out-run.d.ts.map +1 -1
  132. package/dist/components/fan-out-run.js +82 -14
  133. package/dist/components/fan-out-support.d.ts +6 -0
  134. package/dist/components/fan-out-support.d.ts.map +1 -1
  135. package/dist/components/fan-out-support.js +4 -1
  136. package/dist/components/fan-out.d.ts +28 -0
  137. package/dist/components/fan-out.d.ts.map +1 -1
  138. package/dist/components/groups.d.ts +229 -0
  139. package/dist/components/groups.d.ts.map +1 -0
  140. package/dist/components/groups.js +760 -0
  141. package/dist/components/index.d.ts +7 -2
  142. package/dist/components/index.d.ts.map +1 -1
  143. package/dist/components/index.js +5 -0
  144. package/dist/components/plan-shape-conformance.d.ts +48 -0
  145. package/dist/components/plan-shape-conformance.d.ts.map +1 -0
  146. package/dist/components/plan-shape-conformance.js +122 -0
  147. package/dist/components/pr-run.d.ts +62 -7
  148. package/dist/components/pr-run.d.ts.map +1 -1
  149. package/dist/components/pr-run.js +354 -53
  150. package/dist/components/promote-rounds.d.ts +9 -0
  151. package/dist/components/promote-rounds.d.ts.map +1 -1
  152. package/dist/components/promote-rounds.js +23 -2
  153. package/dist/components/promote.d.ts +17 -0
  154. package/dist/components/promote.d.ts.map +1 -1
  155. package/dist/components/promote.js +36 -6
  156. package/dist/components/release-gate.d.ts +23 -1
  157. package/dist/components/release-gate.d.ts.map +1 -1
  158. package/dist/components/release-gate.js +97 -33
  159. package/dist/components/run-groups.d.ts +58 -0
  160. package/dist/components/run-groups.d.ts.map +1 -0
  161. package/dist/components/run-groups.js +78 -0
  162. package/dist/components/starter-plugin.d.ts +1 -0
  163. package/dist/components/starter-plugin.d.ts.map +1 -1
  164. package/dist/components/starter-plugin.js +6 -1
  165. package/dist/components/verbs/index.d.ts +1 -0
  166. package/dist/components/verbs/index.d.ts.map +1 -1
  167. package/dist/components/verbs/index.js +1 -0
  168. package/dist/components/verbs/member-outputs.d.ts +187 -0
  169. package/dist/components/verbs/member-outputs.d.ts.map +1 -0
  170. package/dist/components/verbs/member-outputs.js +218 -0
  171. package/dist/components/wave-plan.d.ts +2 -0
  172. package/dist/components/wave-plan.d.ts.map +1 -1
  173. package/dist/components/wave-plan.js +4 -1
  174. package/dist/config.d.ts +60 -0
  175. package/dist/config.d.ts.map +1 -1
  176. package/dist/config.js +27 -0
  177. package/dist/deep-observation.d.ts +1 -13
  178. package/dist/deep-observation.d.ts.map +1 -1
  179. package/dist/deep-observation.js +5 -22
  180. package/dist/drift-record.d.ts +542 -0
  181. package/dist/drift-record.d.ts.map +1 -0
  182. package/dist/drift-record.js +886 -0
  183. package/dist/engine-facts.d.ts +144 -0
  184. package/dist/engine-facts.d.ts.map +1 -0
  185. package/dist/engine-facts.js +14 -0
  186. package/dist/graph-ir.d.ts +153 -2
  187. package/dist/graph-ir.d.ts.map +1 -1
  188. package/dist/graph-ir.js +224 -6
  189. package/dist/graph-refs.d.ts +145 -0
  190. package/dist/graph-refs.d.ts.map +1 -1
  191. package/dist/graph-refs.js +305 -0
  192. package/dist/graph-schema/fixtures/adopt.json +15 -0
  193. package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  194. package/dist/graph-schema/fixtures/index.json +68 -0
  195. package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  196. package/dist/graph-schema/fixtures.d.ts +23 -0
  197. package/dist/graph-schema/fixtures.d.ts.map +1 -0
  198. package/dist/graph-schema/fixtures.js +246 -0
  199. package/dist/graph-schema/generate.d.ts +15 -0
  200. package/dist/graph-schema/generate.d.ts.map +1 -0
  201. package/dist/graph-schema/generate.js +267 -0
  202. package/dist/graph-schema/graph.schema.json +1387 -0
  203. package/dist/health.schema.json +2 -1
  204. package/dist/identity.d.ts.map +1 -1
  205. package/dist/identity.js +2 -0
  206. package/dist/index.d.ts +1 -0
  207. package/dist/index.d.ts.map +1 -1
  208. package/dist/index.js +2 -0
  209. package/dist/instance-nodes.d.ts +159 -0
  210. package/dist/instance-nodes.d.ts.map +1 -0
  211. package/dist/instance-nodes.js +231 -0
  212. package/dist/lexicon.d.ts +72 -0
  213. package/dist/lexicon.d.ts.map +1 -1
  214. package/dist/lifecycle/adoption-file.d.ts +56 -0
  215. package/dist/lifecycle/adoption-file.d.ts.map +1 -0
  216. package/dist/lifecycle/adoption-file.js +123 -0
  217. package/dist/lifecycle/cloud-index.d.ts +186 -0
  218. package/dist/lifecycle/cloud-index.d.ts.map +1 -0
  219. package/dist/lifecycle/cloud-index.js +618 -0
  220. package/dist/lifecycle/derive-edges.d.ts +59 -0
  221. package/dist/lifecycle/derive-edges.d.ts.map +1 -0
  222. package/dist/lifecycle/derive-edges.js +126 -0
  223. package/dist/lifecycle/git.d.ts +10 -9
  224. package/dist/lifecycle/git.d.ts.map +1 -1
  225. package/dist/lifecycle/git.js +7 -1
  226. package/dist/lifecycle/identity-match.d.ts +133 -0
  227. package/dist/lifecycle/identity-match.d.ts.map +1 -0
  228. package/dist/lifecycle/identity-match.js +147 -0
  229. package/dist/lifecycle/index-diff.d.ts +224 -0
  230. package/dist/lifecycle/index-diff.d.ts.map +1 -0
  231. package/dist/lifecycle/index-diff.js +327 -0
  232. package/dist/lifecycle/index-document.d.ts +130 -0
  233. package/dist/lifecycle/index-document.d.ts.map +1 -0
  234. package/dist/lifecycle/index-document.js +189 -0
  235. package/dist/lifecycle/index-drift.d.ts +121 -0
  236. package/dist/lifecycle/index-drift.d.ts.map +1 -0
  237. package/dist/lifecycle/index-drift.js +178 -0
  238. package/dist/lifecycle/index-import.d.ts +168 -0
  239. package/dist/lifecycle/index-import.d.ts.map +1 -0
  240. package/dist/lifecycle/index-import.js +515 -0
  241. package/dist/lifecycle/index-join.d.ts +129 -0
  242. package/dist/lifecycle/index-join.d.ts.map +1 -0
  243. package/dist/lifecycle/index-join.js +248 -0
  244. package/dist/lifecycle/index.d.ts +4 -0
  245. package/dist/lifecycle/index.d.ts.map +1 -1
  246. package/dist/lifecycle/index.js +4 -0
  247. package/dist/lifecycle/observation-baseline.d.ts +61 -21
  248. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  249. package/dist/lifecycle/observation-baseline.js +144 -19
  250. package/dist/lifecycle/observe.d.ts.map +1 -1
  251. package/dist/lifecycle/observe.js +6 -1
  252. package/dist/lifecycle/query.d.ts +45 -4
  253. package/dist/lifecycle/query.d.ts.map +1 -1
  254. package/dist/lifecycle/query.js +89 -7
  255. package/dist/lifecycle/replay.d.ts +10 -3
  256. package/dist/lifecycle/replay.d.ts.map +1 -1
  257. package/dist/lifecycle/replay.js +10 -6
  258. package/dist/lifecycle/scenario.d.ts +4 -3
  259. package/dist/lifecycle/scenario.d.ts.map +1 -1
  260. package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
  261. package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
  262. package/dist/lifecycle/snapshot-store-fake.js +21 -0
  263. package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
  264. package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
  265. package/dist/lifecycle/snapshot-store-s3.js +254 -0
  266. package/dist/lifecycle/snapshot-store.d.ts +170 -0
  267. package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
  268. package/dist/lifecycle/snapshot-store.js +832 -0
  269. package/dist/lifecycle/snapshot.d.ts +11 -0
  270. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  271. package/dist/lifecycle/snapshot.js +18 -5
  272. package/dist/lifecycle/type-listing.d.ts +148 -0
  273. package/dist/lifecycle/type-listing.d.ts.map +1 -0
  274. package/dist/lifecycle/type-listing.js +166 -0
  275. package/dist/lint/component-checks.d.ts +15 -1
  276. package/dist/lint/component-checks.d.ts.map +1 -1
  277. package/dist/lint/component-checks.js +1 -0
  278. package/dist/lint/presets/relaxed.json +2 -1
  279. package/dist/lint/presets/strict.json +2 -1
  280. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
  281. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
  282. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
  283. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
  284. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
  285. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
  286. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
  287. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
  288. package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
  289. package/dist/lint/rules/comp/index.d.ts +2 -1
  290. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  291. package/dist/lint/rules/comp/index.js +4 -1
  292. package/dist/observation.d.ts +19 -2
  293. package/dist/observation.d.ts.map +1 -1
  294. package/dist/observation.js +18 -1
  295. package/dist/op/activities/activity-contracts.d.ts +1 -1
  296. package/dist/op/activities/activity-contracts.js +1 -1
  297. package/dist/op/activities/change-set.d.ts +6 -4
  298. package/dist/op/activities/change-set.d.ts.map +1 -1
  299. package/dist/op/activities/change-set.js +4 -2
  300. package/dist/op/activity-failure.d.ts +27 -0
  301. package/dist/op/activity-failure.d.ts.map +1 -1
  302. package/dist/op/activity-failure.js +31 -0
  303. package/dist/op/index.d.ts +2 -1
  304. package/dist/op/index.d.ts.map +1 -1
  305. package/dist/op/index.js +1 -1
  306. package/dist/op/local-executor.d.ts +5 -1
  307. package/dist/op/local-executor.d.ts.map +1 -1
  308. package/dist/op/local-executor.js +22 -8
  309. package/dist/pr-loop.d.ts +92 -3
  310. package/dist/pr-loop.d.ts.map +1 -1
  311. package/dist/pr-loop.js +62 -3
  312. package/dist/pulumi-urn.d.ts +101 -0
  313. package/dist/pulumi-urn.d.ts.map +1 -0
  314. package/dist/pulumi-urn.js +131 -0
  315. package/dist/release-floors.d.ts +23 -0
  316. package/dist/release-floors.d.ts.map +1 -0
  317. package/dist/release-floors.js +22 -0
  318. package/dist/sensitive-key.d.ts +23 -0
  319. package/dist/sensitive-key.d.ts.map +1 -0
  320. package/dist/sensitive-key.js +31 -0
  321. package/dist/wave-runtime.mjs +234 -234
  322. package/dist/workspace/adoption-report.d.ts +53 -0
  323. package/dist/workspace/adoption-report.d.ts.map +1 -0
  324. package/dist/workspace/adoption-report.js +76 -0
  325. package/dist/workspace/capabilities-contract.d.ts +7 -0
  326. package/dist/workspace/capabilities-contract.d.ts.map +1 -1
  327. package/dist/workspace/capabilities-contract.js +2 -1
  328. package/dist/workspace/capabilities.schema.json +98 -0
  329. package/dist/workspace/change-set.schema.json +42 -3
  330. package/dist/workspace/ci-cloud-identity.d.ts +13 -1
  331. package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
  332. package/dist/workspace/ci-cloud-identity.js +30 -1
  333. package/dist/workspace/compose-graph.d.ts +10 -1
  334. package/dist/workspace/compose-graph.d.ts.map +1 -1
  335. package/dist/workspace/compose-graph.js +6 -1
  336. package/dist/workspace/conformance/index.d.ts +1 -1
  337. package/dist/workspace/conformance/index.d.ts.map +1 -1
  338. package/dist/workspace/conformance/index.js +6 -1
  339. package/dist/workspace/drift-cli.d.ts +81 -0
  340. package/dist/workspace/drift-cli.d.ts.map +1 -0
  341. package/dist/workspace/drift-cli.js +317 -0
  342. package/dist/workspace/drift.schema.json +672 -0
  343. package/dist/workspace/graph-cli.d.ts +2 -0
  344. package/dist/workspace/graph-cli.d.ts.map +1 -1
  345. package/dist/workspace/graph-cli.js +3 -0
  346. package/dist/workspace/graph.schema.json +72 -0
  347. package/dist/workspace/init.d.ts +5 -1
  348. package/dist/workspace/init.d.ts.map +1 -1
  349. package/dist/workspace/init.js +55 -9
  350. package/dist/workspace/ledger.schema.json +1 -1
  351. package/dist/workspace/nested-graph.d.ts.map +1 -1
  352. package/dist/workspace/nested-graph.js +2 -0
  353. package/dist/workspace/pr-report.schema.json +76 -3
  354. package/dist/workspace/promote-waves.d.ts +112 -0
  355. package/dist/workspace/promote-waves.d.ts.map +1 -0
  356. package/dist/workspace/promote-waves.js +290 -0
  357. package/dist/workspace/promote.d.ts +84 -2
  358. package/dist/workspace/promote.d.ts.map +1 -1
  359. package/dist/workspace/promote.js +347 -61
  360. package/dist/workspace/reason-codes.d.ts +12 -0
  361. package/dist/workspace/reason-codes.d.ts.map +1 -1
  362. package/dist/workspace/reason-codes.js +14 -0
  363. package/dist/workspace/search-contract.d.ts +3 -3
  364. package/dist/workspace/search-contract.d.ts.map +1 -1
  365. package/dist/workspace/search-contract.js +2 -0
  366. package/dist/workspace/search.schema.json +103 -5
  367. package/package.json +14 -1
  368. package/src/__fixtures__/drift-record/README.md +58 -0
  369. package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
  370. package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
  371. package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
  372. package/src/__fixtures__/drift-record/component-status.json +91 -0
  373. package/src/__fixtures__/drift-record/deep-diff.json +259 -0
  374. package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
  375. package/src/__fixtures__/drift-record/live-diff.json +125 -0
  376. package/src/audit/discover.e2e.test.ts +14 -0
  377. package/src/audit/discover.ts +3 -1
  378. package/src/behaviour.ts +1 -1
  379. package/src/capabilities/templates.json +145 -0
  380. package/src/capabilities/templates.test.ts +212 -0
  381. package/src/capabilities/templates.ts +204 -0
  382. package/src/change-set-bundle.test.ts +83 -0
  383. package/src/change-set-graph.ts +80 -9
  384. package/src/change-set.test.ts +24 -0
  385. package/src/change-set.ts +77 -14
  386. package/src/ci-identity-targets.test.ts +23 -0
  387. package/src/ci-identity.ts +10 -4
  388. package/src/cli/commands/import-index.test.ts +117 -0
  389. package/src/cli/commands/import-index.ts +265 -0
  390. package/src/cli/commands/lint.ts +4 -2
  391. package/src/cli/handlers/components.test.ts +9 -0
  392. package/src/cli/handlers/components.ts +12 -2
  393. package/src/cli/handlers/fan-out.ts +17 -3
  394. package/src/cli/handlers/graph.test.ts +42 -0
  395. package/src/cli/handlers/graph.ts +26 -4
  396. package/src/cli/handlers/index-cloud.ts +32 -0
  397. package/src/cli/handlers/index-diff.test.ts +68 -0
  398. package/src/cli/handlers/index-diff.ts +146 -0
  399. package/src/cli/handlers/ledger-root-member.test.ts +161 -0
  400. package/src/cli/handlers/lifecycle-drift.ts +68 -0
  401. package/src/cli/handlers/lifecycle.test.ts +33 -11
  402. package/src/cli/handlers/lifecycle.ts +205 -30
  403. package/src/cli/handlers/misc.ts +73 -0
  404. package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
  405. package/src/cli/handlers/pr.ts +16 -3
  406. package/src/cli/handlers/promote.test.ts +64 -0
  407. package/src/cli/handlers/promote.ts +81 -6
  408. package/src/cli/handlers/query.ts +27 -4
  409. package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
  410. package/src/cli/handlers/run-groups-cli.test.ts +229 -0
  411. package/src/cli/handlers/scenario.test.ts +13 -0
  412. package/src/cli/handlers/scenario.ts +8 -7
  413. package/src/cli/handlers/search-index.test.ts +255 -0
  414. package/src/cli/handlers/search-json.test.ts +1 -1
  415. package/src/cli/handlers/search-ownership.test.ts +194 -0
  416. package/src/cli/handlers/search.ts +271 -38
  417. package/src/cli/main.test.ts +7 -0
  418. package/src/cli/main.ts +56 -1
  419. package/src/cli/mcp/lifecycle-tools.ts +3 -2
  420. package/src/cli/mcp/resource-handlers.ts +4 -3
  421. package/src/cli/mcp/workspace-tools.ts +1 -1
  422. package/src/cli/parse-args.ts +40 -2
  423. package/src/cli/registry.ts +19 -1
  424. package/src/cloud-lister/aws.test.ts +202 -0
  425. package/src/cloud-lister/aws.ts +226 -0
  426. package/src/cloud-lister/azure.test.ts +132 -0
  427. package/src/cloud-lister/azure.ts +142 -0
  428. package/src/cloud-lister/common.ts +115 -0
  429. package/src/cloud-lister/credentials.ts +110 -0
  430. package/src/cloud-lister/fakes.ts +66 -0
  431. package/src/cloud-lister/gcp.test.ts +121 -0
  432. package/src/cloud-lister/gcp.ts +159 -0
  433. package/src/cloud-lister/guard.test.ts +198 -0
  434. package/src/cloud-lister/guard.ts +111 -0
  435. package/src/cloud-lister/ids.ts +54 -0
  436. package/src/cloud-lister/index.ts +134 -0
  437. package/src/cloud-lister/kubernetes.test.ts +160 -0
  438. package/src/cloud-lister/kubernetes.ts +284 -0
  439. package/src/cloud-lister/markers.ts +74 -0
  440. package/src/cloud-lister/mask.ts +77 -0
  441. package/src/cloud-lister/shape.test.ts +81 -0
  442. package/src/cloud-lister/sigv4.ts +70 -0
  443. package/src/cloud-lister/transport.ts +118 -0
  444. package/src/cloud-lister/types.ts +135 -0
  445. package/src/components/capability-plugin-loader.ts +4 -0
  446. package/src/components/capability-plugin.ts +10 -0
  447. package/src/components/capability.ts +238 -0
  448. package/src/components/cli-support.ts +70 -14
  449. package/src/components/covered-round.test.ts +166 -0
  450. package/src/components/covered-round.ts +293 -0
  451. package/src/components/declared-reads.test.ts +103 -0
  452. package/src/components/declared-reads.ts +134 -0
  453. package/src/components/deploy-units.ts +4 -0
  454. package/src/components/driver.ts +101 -4
  455. package/src/components/fan-out-run.ts +95 -15
  456. package/src/components/fan-out-support.ts +10 -1
  457. package/src/components/fan-out.ts +30 -0
  458. package/src/components/groups.test.ts +320 -0
  459. package/src/components/groups.ts +880 -0
  460. package/src/components/index.ts +59 -0
  461. package/src/components/plan-shape-conformance.ts +130 -0
  462. package/src/components/pr-env-run-bundle.test.ts +2 -0
  463. package/src/components/pr-run.test.ts +728 -4
  464. package/src/components/pr-run.ts +395 -55
  465. package/src/components/promote-rounds.ts +25 -2
  466. package/src/components/promote.ts +55 -5
  467. package/src/components/registry.test.ts +1 -1
  468. package/src/components/release-gate.ts +115 -30
  469. package/src/components/release-groups.test.ts +362 -0
  470. package/src/components/run-groups.ts +113 -0
  471. package/src/components/starter-plugin.ts +6 -0
  472. package/src/components/verbs/index.ts +1 -0
  473. package/src/components/verbs/member-outputs.test.ts +205 -0
  474. package/src/components/verbs/member-outputs.ts +308 -0
  475. package/src/components/wave-plan.ts +5 -2
  476. package/src/config.ts +44 -0
  477. package/src/deep-observation.ts +5 -23
  478. package/src/drift-record.test.ts +596 -0
  479. package/src/drift-record.ts +1206 -0
  480. package/src/engine-facts.guard.test.ts +111 -0
  481. package/src/engine-facts.test.ts +178 -0
  482. package/src/engine-facts.ts +154 -0
  483. package/src/gated-waves-bundle.test.ts +2 -0
  484. package/src/graph-ir-live.test.ts +92 -0
  485. package/src/graph-ir.ts +317 -7
  486. package/src/graph-refs.ts +416 -0
  487. package/src/graph-schema/fixtures/adopt.json +15 -0
  488. package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  489. package/src/graph-schema/fixtures/index.json +68 -0
  490. package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  491. package/src/graph-schema/fixtures.ts +267 -0
  492. package/src/graph-schema/generate.ts +263 -0
  493. package/src/graph-schema/graph-schema.test.ts +153 -0
  494. package/src/graph-schema/graph.schema.json +1387 -0
  495. package/src/health.schema.json +2 -1
  496. package/src/identity.ts +2 -0
  497. package/src/index.ts +2 -0
  498. package/src/instance-nodes.ts +288 -0
  499. package/src/lexicon.ts +76 -0
  500. package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
  501. package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
  502. package/src/lifecycle/adoption-file.test.ts +100 -0
  503. package/src/lifecycle/adoption-file.ts +154 -0
  504. package/src/lifecycle/cloud-index.test.ts +517 -0
  505. package/src/lifecycle/cloud-index.ts +736 -0
  506. package/src/lifecycle/derive-edges.test.ts +193 -0
  507. package/src/lifecycle/derive-edges.ts +182 -0
  508. package/src/lifecycle/git.ts +10 -4
  509. package/src/lifecycle/identity-match.test.ts +308 -0
  510. package/src/lifecycle/identity-match.ts +257 -0
  511. package/src/lifecycle/index-diff.test.ts +198 -0
  512. package/src/lifecycle/index-diff.ts +476 -0
  513. package/src/lifecycle/index-document.test.ts +64 -0
  514. package/src/lifecycle/index-document.ts +299 -0
  515. package/src/lifecycle/index-drift.test.ts +191 -0
  516. package/src/lifecycle/index-drift.ts +304 -0
  517. package/src/lifecycle/index-import.test.ts +255 -0
  518. package/src/lifecycle/index-import.ts +634 -0
  519. package/src/lifecycle/index-join.test.ts +250 -0
  520. package/src/lifecycle/index-join.ts +304 -0
  521. package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
  522. package/src/lifecycle/index.ts +4 -0
  523. package/src/lifecycle/member-ledger.test.ts +25 -7
  524. package/src/lifecycle/observation-baseline.test.ts +72 -1
  525. package/src/lifecycle/observation-baseline.ts +190 -27
  526. package/src/lifecycle/observe.ts +6 -1
  527. package/src/lifecycle/query.test.ts +69 -1
  528. package/src/lifecycle/query.ts +103 -10
  529. package/src/lifecycle/replay.test.ts +9 -5
  530. package/src/lifecycle/replay.ts +9 -5
  531. package/src/lifecycle/scenario.ts +4 -3
  532. package/src/lifecycle/snapshot-store-fake.ts +29 -0
  533. package/src/lifecycle/snapshot-store-s3.ts +316 -0
  534. package/src/lifecycle/snapshot-store.test.ts +385 -0
  535. package/src/lifecycle/snapshot-store.ts +981 -0
  536. package/src/lifecycle/snapshot.test.ts +13 -0
  537. package/src/lifecycle/snapshot.ts +23 -6
  538. package/src/lifecycle/type-listing.test.ts +166 -0
  539. package/src/lifecycle/type-listing.ts +309 -0
  540. package/src/lint/component-checks.ts +17 -1
  541. package/src/lint/config.test.ts +3 -0
  542. package/src/lint/presets/relaxed.json +2 -1
  543. package/src/lint/presets/strict.json +2 -1
  544. package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
  545. package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
  546. package/src/lint/rules/comp/comp.test.ts +2 -1
  547. package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
  548. package/src/lint/rules/comp/comp008.test.ts +77 -0
  549. package/src/lint/rules/comp/index.ts +4 -1
  550. package/src/observation-edges.test.ts +33 -0
  551. package/src/observation.ts +36 -1
  552. package/src/op/activities/activity-contracts.ts +1 -1
  553. package/src/op/activities/change-set.ts +8 -5
  554. package/src/op/activity-failure.ts +36 -0
  555. package/src/op/index.ts +2 -1
  556. package/src/op/local-executor.test.ts +80 -0
  557. package/src/op/local-executor.ts +27 -9
  558. package/src/pr-loop-bundle.test.ts +5 -2
  559. package/src/pr-loop.test.ts +57 -1
  560. package/src/pr-loop.ts +154 -6
  561. package/src/pulumi-urn.ts +151 -0
  562. package/src/release-floors.ts +22 -0
  563. package/src/sensitive-key.ts +34 -0
  564. package/src/workspace/adoption-report.test.ts +61 -0
  565. package/src/workspace/adoption-report.ts +103 -0
  566. package/src/workspace/capabilities-contract.test.ts +4 -0
  567. package/src/workspace/capabilities-contract.ts +9 -1
  568. package/src/workspace/capabilities.schema.json +98 -0
  569. package/src/workspace/change-set.schema.json +42 -3
  570. package/src/workspace/ci-cloud-identity.ts +31 -1
  571. package/src/workspace/compose-graph.test.ts +12 -0
  572. package/src/workspace/compose-graph.ts +12 -1
  573. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  574. package/src/workspace/conformance/index.ts +6 -1
  575. package/src/workspace/drift-cli.test.ts +65 -0
  576. package/src/workspace/drift-cli.ts +365 -0
  577. package/src/workspace/drift.schema.json +672 -0
  578. package/src/workspace/graph-cli.ts +5 -0
  579. package/src/workspace/graph.schema.json +72 -0
  580. package/src/workspace/init.test.ts +38 -1
  581. package/src/workspace/init.ts +50 -8
  582. package/src/workspace/intent-gaps.test.ts +18 -2
  583. package/src/workspace/ledger.schema.json +1 -1
  584. package/src/workspace/nested-graph.ts +1 -0
  585. package/src/workspace/pr-report.schema.json +76 -3
  586. package/src/workspace/promote-waves.test.ts +296 -0
  587. package/src/workspace/promote-waves.ts +340 -0
  588. package/src/workspace/promote.test.ts +158 -4
  589. package/src/workspace/promote.ts +398 -57
  590. package/src/workspace/reason-codes.test.ts +4 -1
  591. package/src/workspace/reason-codes.ts +14 -0
  592. package/src/workspace/search-contract.ts +2 -0
  593. package/src/workspace/search.schema.json +103 -5
@@ -0,0 +1,226 @@
1
+ /**
2
+ * behold#1029: AWS, through Cloud Control `ListResources`, one call (and its
3
+ * pages) per type and region.
4
+ *
5
+ * Cloud Control lists one type at a time, so `types: "*"` lists
6
+ * {@link DEFAULT_AWS_TYPES} and says so in a note; every other type is left
7
+ * without a coverage entry, which reads as not observed. Regions must be
8
+ * named: listing "every region" would need a call outside the allowlist.
9
+ * Global types (IAM, CloudFront, Route 53, Organizations, Global Accelerator)
10
+ * are listed once, from us-east-1, with location `global`.
11
+ *
12
+ * A tag filter is applied to the listed objects here, because
13
+ * `ListResources` takes none: a filtered run costs the same calls as a full
14
+ * one (#1029's listing-cost note).
15
+ *
16
+ * The pinned floci (ghcr.io/lex00/floci, source c688f52ee) serves
17
+ * `ListResources` with no paging and answers a type it does not model with an
18
+ * empty list rather than an error, so on floci an empty `listed` entry is
19
+ * weaker evidence than on AWS. See behold#1029's emulator comment.
20
+ */
21
+
22
+ import { buildResource, describeFailure, listedVerdict, noteTagFilter, withThrottleRetry, type ListerContext } from "./common";
23
+ import { resolveAwsCredentials, ListerConfigError, type AwsStaticCredentials, type CredentialChains } from "./credentials";
24
+ import { signV4 } from "./sigv4";
25
+ import { jsonBody, type TlsOptions, type WireResponse } from "./transport";
26
+ import type { CloudId } from "./types";
27
+
28
+ export interface AwsTarget {
29
+ /** An emulator's endpoint, e.g. `http://localhost:4566`. Absent means AWS's own regional endpoint. */
30
+ endpoint?: string;
31
+ credentials?: AwsStaticCredentials;
32
+ tls?: TlsOptions;
33
+ }
34
+
35
+ /** What `types: "*"` lists: common types Cloud Control lists without a parent identifier. */
36
+ export const DEFAULT_AWS_TYPES: readonly string[] = [
37
+ "AWS::CloudFront::Distribution",
38
+ "AWS::DynamoDB::Table",
39
+ "AWS::EC2::Instance",
40
+ "AWS::EC2::InternetGateway",
41
+ "AWS::EC2::LaunchTemplate",
42
+ "AWS::EC2::NatGateway",
43
+ "AWS::EC2::RouteTable",
44
+ "AWS::EC2::SecurityGroup",
45
+ "AWS::EC2::Subnet",
46
+ "AWS::EC2::VPC",
47
+ "AWS::ECR::Repository",
48
+ "AWS::ECS::Cluster",
49
+ "AWS::EKS::Cluster",
50
+ "AWS::ElasticLoadBalancingV2::LoadBalancer",
51
+ "AWS::Events::EventBus",
52
+ "AWS::IAM::InstanceProfile",
53
+ "AWS::IAM::ManagedPolicy",
54
+ "AWS::IAM::Role",
55
+ "AWS::IAM::User",
56
+ "AWS::KMS::Key",
57
+ "AWS::Lambda::Function",
58
+ "AWS::Logs::LogGroup",
59
+ "AWS::RDS::DBCluster",
60
+ "AWS::RDS::DBInstance",
61
+ "AWS::Route53::HostedZone",
62
+ "AWS::S3::Bucket",
63
+ "AWS::SNS::Topic",
64
+ "AWS::SQS::Queue",
65
+ "AWS::SecretsManager::Secret",
66
+ "AWS::StepFunctions::StateMachine",
67
+ ];
68
+
69
+ const GLOBAL_TYPE_PREFIXES = ["AWS::IAM::", "AWS::CloudFront::", "AWS::Route53::", "AWS::Organizations::", "AWS::GlobalAccelerator::"];
70
+
71
+ export function isGlobalAwsType(type: string): boolean {
72
+ return GLOBAL_TYPE_PREFIXES.some((p) => type.startsWith(p));
73
+ }
74
+
75
+ const GLOBAL_SIGNING_REGION = "us-east-1";
76
+ const MAX_PAGES = 1000;
77
+
78
+ export function cloudControlUrl(target: AwsTarget, region: string): string {
79
+ if (target.endpoint !== undefined) return new URL("/", target.endpoint).toString();
80
+ return `https://cloudcontrolapi.${region}.amazonaws.com/`;
81
+ }
82
+
83
+ interface AwsError {
84
+ code: string;
85
+ message: string;
86
+ }
87
+
88
+ function awsError(res: WireResponse): AwsError {
89
+ const body = jsonBody(res) as Record<string, unknown> | undefined;
90
+ const raw = String(body?.__type ?? body?.code ?? res.headers["x-amzn-errortype"] ?? "");
91
+ const code = raw.includes("#") ? raw.slice(raw.lastIndexOf("#") + 1) : raw.split(":")[0];
92
+ const message = String(body?.message ?? body?.Message ?? "");
93
+ return { code, message };
94
+ }
95
+
96
+ const CREDENTIAL_CODES = /^(AccessDenied|AccessDeniedException|UnrecognizedClientException|InvalidClientTokenId|ExpiredToken|ExpiredTokenException|InvalidSignatureException|SignatureDoesNotMatch|MissingAuthenticationToken)$/;
97
+
98
+ function tagsOf(props: Record<string, unknown>): Record<string, string> {
99
+ const raw = props.Tags;
100
+ const out: Record<string, string> = {};
101
+ if (Array.isArray(raw)) {
102
+ for (const t of raw) {
103
+ if (t && typeof t === "object") {
104
+ const o = t as Record<string, unknown>;
105
+ if (typeof o.Key === "string") out[o.Key] = String(o.Value ?? "");
106
+ }
107
+ }
108
+ } else if (raw && typeof raw === "object") {
109
+ for (const [k, v] of Object.entries(raw as Record<string, unknown>)) out[k] = String(v ?? "");
110
+ }
111
+ return out;
112
+ }
113
+
114
+ function matchesTagFilter(tags: Record<string, string>, filter: Readonly<Record<string, string>> | undefined): boolean {
115
+ if (!filter) return true;
116
+ return Object.entries(filter).every(([k, v]) => tags[k] === v);
117
+ }
118
+
119
+ export async function listAws(
120
+ ctx: ListerContext,
121
+ target: AwsTarget,
122
+ chains: CredentialChains,
123
+ retryDelays?: readonly number[],
124
+ ): Promise<void> {
125
+ const { scope } = ctx;
126
+ if (scope.locations === "*") {
127
+ throw new ListerConfigError("aws needs its regions named: listing every region would need ec2:DescribeRegions, which the lister does not call");
128
+ }
129
+ const credentials = await resolveAwsCredentials(target, chains);
130
+ let types: readonly string[];
131
+ if (scope.types === "*") {
132
+ types = DEFAULT_AWS_TYPES;
133
+ ctx.notes.push({
134
+ code: "default-types",
135
+ message: `Cloud Control lists one type per call, so types "*" listed ${DEFAULT_AWS_TYPES.length} default types; every other type has no coverage entry and reads as not observed. Name the types to list others.`,
136
+ });
137
+ } else {
138
+ types = scope.types;
139
+ }
140
+ noteTagFilter(ctx, "to the listed objects, after a full ListResources per type (Cloud Control takes no tag filter)");
141
+
142
+ for (const type of types) {
143
+ const global = isGlobalAwsType(type);
144
+ const regions = global ? [GLOBAL_SIGNING_REGION] : scope.locations;
145
+ for (const region of regions) {
146
+ const location = global ? "global" : region;
147
+ await listOne(ctx, target, credentials, type, region, location, retryDelays);
148
+ }
149
+ }
150
+ }
151
+
152
+ async function listOne(
153
+ ctx: ListerContext,
154
+ target: AwsTarget,
155
+ credentials: AwsStaticCredentials,
156
+ type: string,
157
+ region: string,
158
+ location: string,
159
+ retryDelays?: readonly number[],
160
+ ): Promise<void> {
161
+ const url = cloudControlUrl(target, region);
162
+ let nextToken: string | undefined;
163
+ const found: Parameters<typeof buildResource>[1][] = [];
164
+ for (let page = 0; page < MAX_PAGES; page++) {
165
+ const body = JSON.stringify({ TypeName: type, MaxResults: 100, ...(nextToken ? { NextToken: nextToken } : {}) });
166
+ const send = () => {
167
+ const headers = signV4({
168
+ method: "POST",
169
+ url,
170
+ headers: { "content-type": "application/x-amz-json-1.0", "x-amz-target": "CloudApiService.ListResources" },
171
+ body,
172
+ region,
173
+ service: "cloudcontrolapi",
174
+ credentials,
175
+ now: ctx.now(),
176
+ });
177
+ return ctx.transport.send({ substrate: "aws", method: "POST", url, headers, body });
178
+ };
179
+ const res = await withThrottleRetry(send, (r) => r.status === 429 || (r.status >= 400 && /Throttl/.test(awsError(r).code)), retryDelays);
180
+ const observedAt = ctx.now().toISOString();
181
+ if (res.status >= 400) {
182
+ recordFailure(ctx, type, location, res);
183
+ return;
184
+ }
185
+ const parsed = jsonBody(res) as { ResourceDescriptions?: Array<{ Identifier?: string; Properties?: string }>; NextToken?: string } | undefined;
186
+ for (const d of parsed?.ResourceDescriptions ?? []) {
187
+ if (!d.Identifier) continue;
188
+ let props: Record<string, unknown> = {};
189
+ try {
190
+ const p = typeof d.Properties === "string" ? JSON.parse(d.Properties) : d.Properties;
191
+ if (p && typeof p === "object") props = p as Record<string, unknown>;
192
+ } catch {
193
+ props = {};
194
+ }
195
+ const tags = tagsOf(props);
196
+ if (!matchesTagFilter(tags, ctx.scope.tagFilter)) continue;
197
+ const cloudIds: CloudId[] = [];
198
+ if (d.Identifier.startsWith("arn:")) cloudIds.push({ kind: "arn", value: d.Identifier });
199
+ if (typeof props.Arn === "string" && props.Arn.startsWith("arn:")) cloudIds.push({ kind: "arn", value: props.Arn });
200
+ cloudIds.push({ kind: "import-id", value: d.Identifier });
201
+ found.push({ listedType: type, location, nativeId: d.Identifier, cloudIds, tags, properties: props, observedAt, labels: false });
202
+ }
203
+ nextToken = parsed?.NextToken || undefined;
204
+ if (!nextToken) break;
205
+ }
206
+ for (const r of found) ctx.resources.push(buildResource(ctx, r));
207
+ ctx.coverage.push({ listedType: type, location, ...listedVerdict(ctx.scope) });
208
+ }
209
+
210
+ function recordFailure(ctx: ListerContext, type: string, location: string, res: WireResponse): void {
211
+ const err = awsError(res);
212
+ const detail = describeFailure(res, err.code, err.message);
213
+ if (err.code === "UnsupportedActionException" || err.code === "TypeNotFoundException") {
214
+ ctx.coverage.push({ listedType: type, location, verdict: "unsupported-kind", detail });
215
+ ctx.notes.push({ code: "no-lister", listedType: type, location, message: `Cloud Control cannot list ${type}: ${detail}` });
216
+ } else if (err.code === "InvalidRequestException" && /ResourceModel|parent/i.test(err.message)) {
217
+ ctx.coverage.push({ listedType: type, location, verdict: "unsupported-kind", detail: `type needs a parent identifier: ${detail}` });
218
+ ctx.notes.push({ code: "needs-parent", listedType: type, location, message: `${type} lists only under a parent identifier (a ResourceModel), which an account-wide listing does not have: ${detail}` });
219
+ } else if (res.status === 401 || res.status === 403 || CREDENTIAL_CODES.test(err.code)) {
220
+ ctx.coverage.push({ listedType: type, location, verdict: "no-credentials", detail });
221
+ ctx.notes.push({ code: "no-credentials", listedType: type, location, message: `the credentials cannot call cloudformation:ListResources for ${type}: ${detail}` });
222
+ } else {
223
+ ctx.coverage.push({ listedType: type, location, verdict: "read-failed", detail });
224
+ ctx.notes.push({ code: "read-failed", listedType: type, location, message: `ListResources for ${type} in ${location} failed: ${detail}` });
225
+ }
226
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * behold#1029: the Azure lister against a fake Resource Graph. Shapes follow
3
+ * the pinned floci-az's docs/services/resource-graph.md (source 7f079c2):
4
+ * `{ totalRecords, count, data, facets, resultTruncated }`, `$skipToken`
5
+ * for the next page, `type` lowercase.
6
+ */
7
+ import { describe, expect, it } from "vitest";
8
+ import { listCloud } from "./index";
9
+ import { resourceGraphQuery } from "./azure";
10
+ import { ListerConfigError } from "./credentials";
11
+ import { MASKED } from "./mask";
12
+ import { fakeFetcher, json, refusingChains, steppingClock } from "./fakes";
13
+
14
+ const SUB = "00000000-0000-0000-0000-000000000001";
15
+ const ENDPOINT = "https://localhost:4577";
16
+ const ID_A = `/subscriptions/${SUB}/resourceGroups/RG-Web/providers/Microsoft.Storage/storageAccounts/webassets`;
17
+ const ID_B = `/subscriptions/${SUB}/resourceGroups/rg-web/providers/Microsoft.KeyVault/vaults/kv1`;
18
+
19
+ describe("the Resource Graph query", () => {
20
+ it("narrows by type, location and tag, and orders by id", () => {
21
+ const q = resourceGraphQuery({
22
+ substrate: "azure",
23
+ scopeId: SUB,
24
+ types: ["microsoft.storage/storageaccounts"],
25
+ locations: ["eastus"],
26
+ tagFilter: { "tofu-estate": "web" },
27
+ });
28
+ expect(q).toBe(
29
+ [
30
+ "Resources",
31
+ "| where type in~ ('microsoft.storage/storageaccounts')",
32
+ "| where location in~ ('eastus')",
33
+ "| where tags['tofu-estate'] == 'web'",
34
+ "| project id, name, type, kind, location, resourceGroup, subscriptionId, managedBy, tags, sku, plan, identity, zones, extendedLocation, properties",
35
+ "| order by id asc",
36
+ ].join("\n"),
37
+ );
38
+ });
39
+
40
+ it("adds resource groups for every type", () => {
41
+ expect(resourceGraphQuery({ substrate: "azure", scopeId: SUB, types: "*", locations: "*" }).split("\n")[0]).toBe(
42
+ "Resources | union (ResourceContainers | where type =~ 'microsoft.resources/subscriptions/resourcegroups')",
43
+ );
44
+ });
45
+
46
+ it("refuses a value that could change the query", () => {
47
+ expect(() => resourceGraphQuery({ substrate: "azure", scopeId: SUB, types: ["x') | union (Secrets"], locations: "*" })).toThrow(ListerConfigError);
48
+ });
49
+ });
50
+
51
+ describe("listing azure", () => {
52
+ it("pages and fills the listing-result shape", async () => {
53
+ const fake = fakeFetcher((call) => {
54
+ const body = JSON.parse(call.body ?? "{}");
55
+ expect(body.subscriptions).toEqual([SUB]);
56
+ if (!body.options.$skipToken) {
57
+ return json(200, {
58
+ totalRecords: 2,
59
+ count: 1,
60
+ data: [{ id: ID_A, name: "webassets", type: "microsoft.storage/storageaccounts", location: "eastus", tags: { "chant-managed-by": "chant", "chant-stack": "web" }, properties: { primaryEndpoints: {} } }],
61
+ $skipToken: "1",
62
+ });
63
+ }
64
+ return json(200, {
65
+ totalRecords: 2,
66
+ count: 1,
67
+ data: [{ id: ID_B, name: "kv1", type: "microsoft.keyvault/vaults", location: "EastUS", tags: {}, properties: { adminPassword: "p@ss", connectionString: "Server=x" } }],
68
+ });
69
+ });
70
+ const result = await listCloud(
71
+ { substrate: "azure", scopeId: SUB, types: "*", locations: "*" },
72
+ { substrate: "azure", azure: { endpoint: ENDPOINT, token: "static-token" } },
73
+ { fetcher: fake.fetcher, chains: refusingChains(), now: steppingClock(), retryDelays: [] },
74
+ );
75
+ expect(fake.calls.length).toBe(2);
76
+ expect(fake.calls[0].url).toBe(`${ENDPOINT}/providers/Microsoft.ResourceGraph/resources?api-version=2022-10-01`);
77
+ expect(fake.calls[0].headers.authorization).toBe("Bearer static-token");
78
+
79
+ const sa = result.resources.find((r) => r.nativeId === ID_A)!;
80
+ expect(sa.id).toBe(`azure/${SUB}/eastus/microsoft.storage%2Fstorageaccounts/${ID_A}`);
81
+ expect(sa.cloudIds).toEqual([
82
+ { kind: "provider-id", value: ID_A },
83
+ { kind: "provider-id", value: ID_A.toLowerCase() },
84
+ ]);
85
+ expect(sa.facts.markers).toEqual({ owner: "web", encoding: "plain", source: "live" });
86
+ expect(sa.properties).not.toHaveProperty("tags");
87
+
88
+ const kv = result.resources.find((r) => r.nativeId === ID_B)!;
89
+ expect(kv.location).toBe("eastus");
90
+ expect((kv.properties.properties as Record<string, unknown>).adminPassword).toBe(MASKED);
91
+ expect((kv.properties.properties as Record<string, unknown>).connectionString).toBe(MASKED);
92
+
93
+ expect(result.coverage).toEqual([{ listedType: "*", location: "*", verdict: "listed" }]);
94
+ });
95
+
96
+ it("covers each named type and location", async () => {
97
+ const fake = fakeFetcher(() => json(200, { data: [] }));
98
+ const result = await listCloud(
99
+ { substrate: "azure", scopeId: SUB, types: ["Microsoft.Storage/storageAccounts", "microsoft.keyvault/vaults"], locations: ["westus", "eastus"] },
100
+ { substrate: "azure", azure: { endpoint: ENDPOINT, token: "t" } },
101
+ { fetcher: fake.fetcher, chains: refusingChains(), now: steppingClock(), retryDelays: [] },
102
+ );
103
+ expect(result.coverage.map((c) => `${c.listedType}@${c.location}:${c.verdict}`)).toEqual([
104
+ "microsoft.keyvault/vaults@eastus:listed",
105
+ "microsoft.keyvault/vaults@westus:listed",
106
+ "microsoft.storage/storageaccounts@eastus:listed",
107
+ "microsoft.storage/storageaccounts@westus:listed",
108
+ ]);
109
+ });
110
+
111
+ it("reports a refused token as no-credentials", async () => {
112
+ const fake = fakeFetcher(() => json(401, { error: { code: "InvalidAuthenticationToken", message: "The access token is invalid." } }));
113
+ const result = await listCloud(
114
+ { substrate: "azure", scopeId: SUB, types: "*", locations: "*" },
115
+ { substrate: "azure", azure: { endpoint: ENDPOINT, token: "bad" } },
116
+ { fetcher: fake.fetcher, chains: refusingChains(), now: steppingClock(), retryDelays: [] },
117
+ );
118
+ expect(result.resources).toEqual([]);
119
+ expect(result.coverage).toEqual([{ listedType: "*", location: "*", verdict: "no-credentials", detail: "HTTP 401: InvalidAuthenticationToken: The access token is invalid." }]);
120
+ expect(result.notes[0].code).toBe("no-credentials");
121
+ });
122
+
123
+ it("never asks DefaultAzureCredential when an endpoint is given", async () => {
124
+ const chains = refusingChains();
125
+ const fake = fakeFetcher(() => undefined);
126
+ await expect(
127
+ listCloud({ substrate: "azure", scopeId: SUB, types: "*", locations: "*" }, { substrate: "azure", azure: { endpoint: ENDPOINT } }, { fetcher: fake.fetcher, chains }),
128
+ ).rejects.toThrow(/needs a static bearer token/);
129
+ expect(chains.count.azure).toBe(0);
130
+ expect(fake.calls).toEqual([]);
131
+ });
132
+ });
@@ -0,0 +1,142 @@
1
+ /**
2
+ * behold#1029: Azure, through Resource Graph: one KQL query per run, paged,
3
+ * over the `Resources` table and the subscription's resource groups.
4
+ *
5
+ * Resource Graph answers for every ARM type with one call shape, so coverage
6
+ * is the scope's own types and locations, each `listed` (or `filtered` under
7
+ * a tag filter, which goes into the query). Types are the lowercase ARM types
8
+ * Resource Graph returns (`microsoft.storage/storageaccounts`).
9
+ *
10
+ * Every value that goes into the query (types, locations, tag keys and
11
+ * values) is checked against a plain character set rather than escaped, so a
12
+ * scope can never change the query's shape.
13
+ *
14
+ * The pinned floci-az (ghcr.io/lex00/floci-az, source 7f079c2) serves this
15
+ * call: `POST /providers/Microsoft.ResourceGraph/resources`, the KQL subset
16
+ * used here (`where`, `in~`, `union`, `project`, `order by`), and
17
+ * `$top`/`$skipToken` paging (docs/services/resource-graph.md).
18
+ */
19
+
20
+ import { buildResource, describeFailure, listedVerdict, noteTagFilter, withThrottleRetry, type ListerContext } from "./common";
21
+ import { resolveAzureToken, ListerConfigError, type CredentialChains } from "./credentials";
22
+ import { jsonBody, type TlsOptions, type WireResponse } from "./transport";
23
+ import type { CloudId } from "./types";
24
+
25
+ export interface AzureTarget {
26
+ /** An emulator's ARM endpoint, e.g. `https://localhost:4577`. Absent means `https://management.azure.com`. */
27
+ endpoint?: string;
28
+ /** A bearer token for ARM. Required with an endpoint. */
29
+ token?: string;
30
+ tls?: TlsOptions;
31
+ }
32
+
33
+ export const RESOURCE_GRAPH_API_VERSION = "2022-10-01";
34
+ const RESOURCE_GROUP_TYPE = "microsoft.resources/subscriptions/resourcegroups";
35
+ const PAGE_SIZE = 1000;
36
+ const MAX_PAGES = 10_000;
37
+ const SAFE = /^[A-Za-z0-9._\-\/: ]+$/;
38
+
39
+ function safe(what: string, value: string): string {
40
+ if (!SAFE.test(value)) throw new ListerConfigError(`azure ${what} ${JSON.stringify(value)} has characters a Resource Graph query does not take from a flag`);
41
+ return value;
42
+ }
43
+
44
+ function list(values: readonly string[], what: string): string {
45
+ return values.map((v) => `'${safe(what, v)}'`).join(", ");
46
+ }
47
+
48
+ /** The KQL for a scope. Exported for tests. */
49
+ export function resourceGraphQuery(scope: ListerContext["scope"]): string {
50
+ const lines: string[] = [];
51
+ const wantsGroups = scope.types === "*" || scope.types.some((t) => t.toLowerCase() === RESOURCE_GROUP_TYPE);
52
+ lines.push(wantsGroups ? `Resources | union (ResourceContainers | where type =~ '${RESOURCE_GROUP_TYPE}')` : "Resources");
53
+ if (scope.types !== "*") lines.push(`| where type in~ (${list(scope.types, "type")})`);
54
+ if (scope.locations !== "*") lines.push(`| where location in~ (${list(scope.locations, "location")})`);
55
+ for (const [k, v] of Object.entries(scope.tagFilter ?? {})) {
56
+ lines.push(`| where tags['${safe("tag key", k)}'] == '${safe("tag value", v)}'`);
57
+ }
58
+ lines.push("| project id, name, type, kind, location, resourceGroup, subscriptionId, managedBy, tags, sku, plan, identity, zones, extendedLocation, properties");
59
+ lines.push("| order by id asc");
60
+ return lines.join("\n");
61
+ }
62
+
63
+ export function resourceGraphUrl(target: AzureTarget): string {
64
+ const base = target.endpoint ?? "https://management.azure.com";
65
+ const url = new URL("/providers/Microsoft.ResourceGraph/resources", base);
66
+ url.searchParams.set("api-version", RESOURCE_GRAPH_API_VERSION);
67
+ return url.toString();
68
+ }
69
+
70
+ function azureError(res: WireResponse): { code: string; message: string } {
71
+ const body = jsonBody(res) as { error?: { code?: string; message?: string } } | undefined;
72
+ return { code: body?.error?.code ?? "", message: body?.error?.message ?? "" };
73
+ }
74
+
75
+ export async function listAzure(
76
+ ctx: ListerContext,
77
+ target: AzureTarget,
78
+ chains: CredentialChains,
79
+ retryDelays?: readonly number[],
80
+ ): Promise<void> {
81
+ const { scope } = ctx;
82
+ safe("subscription", scope.scopeId);
83
+ const query = resourceGraphQuery(scope);
84
+ const token = await resolveAzureToken(target, chains);
85
+ const url = resourceGraphUrl(target);
86
+ noteTagFilter(ctx, "in the Resource Graph query");
87
+
88
+ const typesCovered = scope.types === "*" ? ["*"] : [...scope.types].map((t) => t.toLowerCase());
89
+ const locationsCovered = scope.locations === "*" ? ["*"] : [...scope.locations].map((l) => l.toLowerCase());
90
+ const found: Parameters<typeof buildResource>[1][] = [];
91
+ let skipToken: string | undefined;
92
+ for (let page = 0; page < MAX_PAGES; page++) {
93
+ const body = JSON.stringify({
94
+ subscriptions: [scope.scopeId],
95
+ query,
96
+ options: { $top: PAGE_SIZE, resultFormat: "objectArray", ...(skipToken ? { $skipToken: skipToken } : {}) },
97
+ });
98
+ const send = () =>
99
+ ctx.transport.send({
100
+ substrate: "azure",
101
+ method: "POST",
102
+ url,
103
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
104
+ body,
105
+ });
106
+ const res = await withThrottleRetry(send, (r) => r.status === 429, retryDelays);
107
+ const observedAt = ctx.now().toISOString();
108
+ if (res.status >= 400) {
109
+ const err = azureError(res);
110
+ const detail = describeFailure(res, err.code, err.message);
111
+ const verdict = res.status === 401 || res.status === 403 ? "no-credentials" : "read-failed";
112
+ for (const t of typesCovered) for (const l of locationsCovered) ctx.coverage.push({ listedType: t, location: l, verdict, detail });
113
+ ctx.notes.push({
114
+ code: verdict === "no-credentials" ? "no-credentials" : "read-failed",
115
+ message: `Resource Graph query on subscription ${scope.scopeId} failed: ${detail}`,
116
+ });
117
+ return;
118
+ }
119
+ const parsed = jsonBody(res) as { data?: unknown; $skipToken?: string } | undefined;
120
+ const rows = Array.isArray(parsed?.data) ? (parsed!.data as Array<Record<string, unknown>>) : [];
121
+ for (const row of rows) {
122
+ const id = typeof row.id === "string" ? row.id : undefined;
123
+ if (!id) continue;
124
+ const type = String(row.type ?? "").toLowerCase();
125
+ const loc = String(row.location ?? "").toLowerCase() || "global";
126
+ const tags: Record<string, string> = {};
127
+ if (row.tags && typeof row.tags === "object") {
128
+ for (const [k, v] of Object.entries(row.tags as Record<string, unknown>)) tags[k] = String(v ?? "");
129
+ }
130
+ const { tags: _tags, ...properties } = row;
131
+ const cloudIds: CloudId[] = [
132
+ { kind: "provider-id", value: id },
133
+ { kind: "provider-id", value: id.toLowerCase() },
134
+ ];
135
+ found.push({ listedType: type, location: loc, nativeId: id, cloudIds, tags, properties, observedAt, labels: false });
136
+ }
137
+ skipToken = parsed?.$skipToken || undefined;
138
+ if (!skipToken) break;
139
+ }
140
+ for (const r of found) ctx.resources.push(buildResource(ctx, r));
141
+ for (const t of typesCovered) for (const l of locationsCovered) ctx.coverage.push({ listedType: t, location: l, ...listedVerdict(scope) });
142
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * behold#1029: what every substrate's lister shares: building one masked
3
+ * {@link ListedResource}, recording coverage and notes, and the coverage rule
4
+ * for a tag filter.
5
+ */
6
+
7
+ import { listedNodeId } from "./ids";
8
+ import { readMarkers } from "./markers";
9
+ import { maskProperties, maskTags } from "./mask";
10
+ import type { Transport, WireResponse } from "./transport";
11
+ import type { CloudId, CloudScope, CoverageEntry, IndexNote, ListedResource } from "./types";
12
+
13
+ export interface ListerContext {
14
+ scope: CloudScope;
15
+ transport: Transport;
16
+ now: () => Date;
17
+ resources: ListedResource[];
18
+ coverage: CoverageEntry[];
19
+ notes: IndexNote[];
20
+ }
21
+
22
+ export function buildResource(
23
+ ctx: ListerContext,
24
+ r: {
25
+ listedType: string;
26
+ location: string;
27
+ nativeId: string;
28
+ cloudIds: CloudId[];
29
+ tags: Record<string, string>;
30
+ properties: Record<string, unknown>;
31
+ observedAt: string;
32
+ labels: boolean;
33
+ },
34
+ ): ListedResource {
35
+ const tags = maskTags(r.tags);
36
+ const markers = readMarkers(r.tags, { labels: r.labels });
37
+ const cloudIds = dedupeCloudIds(r.cloudIds);
38
+ return {
39
+ id: listedNodeId({ substrate: ctx.scope.substrate, scopeId: ctx.scope.scopeId, location: r.location, listedType: r.listedType, nativeId: r.nativeId }),
40
+ listedType: r.listedType,
41
+ location: r.location,
42
+ nativeId: r.nativeId,
43
+ cloudIds,
44
+ tags,
45
+ properties: maskProperties(r.properties),
46
+ observedAt: r.observedAt,
47
+ facts: { ...(markers ? { markers } : {}), cloudIds, observedAt: r.observedAt },
48
+ };
49
+ }
50
+
51
+ function dedupeCloudIds(ids: CloudId[]): CloudId[] {
52
+ const seen = new Set<string>();
53
+ const out: CloudId[] = [];
54
+ for (const id of ids) {
55
+ if (!id.value) continue;
56
+ const key = `${id.kind}\u0000${id.value}`;
57
+ if (seen.has(key)) continue;
58
+ seen.add(key);
59
+ out.push(id);
60
+ }
61
+ return out;
62
+ }
63
+
64
+ /**
65
+ * The verdict for a (type, location) the lister read in full. Under a tag
66
+ * filter it is `filtered`, because a filtered listing cannot tell an untagged
67
+ * object from a missing one (#1025 "Coverage"); the objects it did list are
68
+ * still observed.
69
+ */
70
+ export function listedVerdict(scope: CloudScope): Pick<CoverageEntry, "verdict" | "detail"> {
71
+ if (scope.tagFilter && Object.keys(scope.tagFilter).length > 0) {
72
+ return { verdict: "filtered", detail: `tag filter ${JSON.stringify(scope.tagFilter)}` };
73
+ }
74
+ return { verdict: "listed" };
75
+ }
76
+
77
+ export function noteTagFilter(ctx: ListerContext, how: string): void {
78
+ if (!ctx.scope.tagFilter || Object.keys(ctx.scope.tagFilter).length === 0) return;
79
+ ctx.notes.push({
80
+ code: "tag-filter",
81
+ message: `the tag filter ${JSON.stringify(ctx.scope.tagFilter)} was applied ${how}; every coverage entry is "filtered", since a tag-filtered listing cannot say an untagged object is absent`,
82
+ });
83
+ }
84
+
85
+ export function sortResult(ctx: ListerContext): void {
86
+ ctx.resources.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
87
+ ctx.coverage.sort((a, b) =>
88
+ a.listedType !== b.listedType ? (a.listedType < b.listedType ? -1 : 1) : a.location < b.location ? -1 : a.location > b.location ? 1 : 0,
89
+ );
90
+ }
91
+
92
+ /** A short, credential-free description of a failed response for a note or a coverage detail. */
93
+ export function describeFailure(res: WireResponse, code?: string, message?: string): string {
94
+ const parts = [`HTTP ${res.status}`];
95
+ if (code) parts.push(code);
96
+ if (message) parts.push(message.length > 300 ? `${message.slice(0, 300)}...` : message);
97
+ return parts.join(": ");
98
+ }
99
+
100
+ export const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
101
+
102
+ /** Retries a throttled call (HTTP 429 or a substrate's throttling code) a few times with backoff. */
103
+ export async function withThrottleRetry(
104
+ send: () => Promise<WireResponse>,
105
+ isThrottled: (res: WireResponse) => boolean,
106
+ delays: readonly number[] = [500, 2000, 5000],
107
+ ): Promise<WireResponse> {
108
+ let res = await send();
109
+ for (const d of delays) {
110
+ if (!isThrottled(res)) return res;
111
+ await sleep(d);
112
+ res = await send();
113
+ }
114
+ return res;
115
+ }