@intentius/chant-lexicon-terraform 0.57.0 → 0.58.0

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 (381) hide show
  1. package/dist/composites/terraform-adopt-op.d.ts +126 -0
  2. package/dist/composites/terraform-adopt-op.d.ts.map +1 -0
  3. package/dist/composites/terraform-apply-op.d.ts +63 -16
  4. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  5. package/dist/composites/terraform-watch-op.d.ts +80 -36
  6. package/dist/composites/terraform-watch-op.d.ts.map +1 -1
  7. package/dist/config.d.ts +49 -2
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/describe-resources.d.ts +256 -16
  10. package/dist/describe-resources.d.ts.map +1 -1
  11. package/dist/hcl/descend.d.ts +98 -0
  12. package/dist/hcl/descend.d.ts.map +1 -0
  13. package/dist/hcl/marker.d.ts +130 -0
  14. package/dist/hcl/marker.d.ts.map +1 -0
  15. package/dist/hcl/parse.d.ts +142 -9
  16. package/dist/hcl/parse.d.ts.map +1 -1
  17. package/dist/hcl/references.d.ts +76 -0
  18. package/dist/hcl/references.d.ts.map +1 -0
  19. package/dist/hcl/roots.d.ts +22 -3
  20. package/dist/hcl/roots.d.ts.map +1 -1
  21. package/dist/hcl/suppressions.d.ts +54 -0
  22. package/dist/hcl/suppressions.d.ts.map +1 -0
  23. package/dist/hcl/value.d.ts +77 -0
  24. package/dist/hcl/value.d.ts.map +1 -0
  25. package/dist/index.d.ts +5 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/integrity.json +32 -5
  28. package/dist/lint/audit-catalog.d.ts +3 -2
  29. package/dist/lint/audit-catalog.d.ts.map +1 -1
  30. package/dist/lint/audit-lineage.d.ts +68 -0
  31. package/dist/lint/audit-lineage.d.ts.map +1 -1
  32. package/dist/lint/post-synth/blocks.d.ts +53 -0
  33. package/dist/lint/post-synth/blocks.d.ts.map +1 -0
  34. package/dist/lint/post-synth/fixtures/load.d.ts +37 -0
  35. package/dist/lint/post-synth/fixtures/load.d.ts.map +1 -0
  36. package/dist/lint/post-synth/index.d.ts.map +1 -1
  37. package/dist/lint/post-synth/module-source.d.ts +38 -0
  38. package/dist/lint/post-synth/module-source.d.ts.map +1 -0
  39. package/dist/lint/post-synth/scope.d.ts +65 -0
  40. package/dist/lint/post-synth/scope.d.ts.map +1 -0
  41. package/dist/lint/post-synth/tf001.d.ts +18 -0
  42. package/dist/lint/post-synth/tf001.d.ts.map +1 -1
  43. package/dist/lint/post-synth/tf002.d.ts +32 -0
  44. package/dist/lint/post-synth/tf002.d.ts.map +1 -0
  45. package/dist/lint/post-synth/tf003.d.ts +22 -0
  46. package/dist/lint/post-synth/tf003.d.ts.map +1 -0
  47. package/dist/lint/post-synth/tf004.d.ts +22 -0
  48. package/dist/lint/post-synth/tf004.d.ts.map +1 -0
  49. package/dist/lint/post-synth/tf005.d.ts +28 -0
  50. package/dist/lint/post-synth/tf005.d.ts.map +1 -0
  51. package/dist/lint/post-synth/tf006.d.ts +21 -0
  52. package/dist/lint/post-synth/tf006.d.ts.map +1 -0
  53. package/dist/lint/post-synth/tf007.d.ts +21 -0
  54. package/dist/lint/post-synth/tf007.d.ts.map +1 -0
  55. package/dist/lint/post-synth/tf008.d.ts +22 -0
  56. package/dist/lint/post-synth/tf008.d.ts.map +1 -0
  57. package/dist/lint/post-synth/tf009.d.ts +24 -0
  58. package/dist/lint/post-synth/tf009.d.ts.map +1 -0
  59. package/dist/lint/post-synth/tf010.d.ts +19 -0
  60. package/dist/lint/post-synth/tf010.d.ts.map +1 -0
  61. package/dist/lint/post-synth/tf011.d.ts +18 -0
  62. package/dist/lint/post-synth/tf011.d.ts.map +1 -0
  63. package/dist/lint/post-synth/tf012.d.ts +17 -0
  64. package/dist/lint/post-synth/tf012.d.ts.map +1 -0
  65. package/dist/lint/post-synth/tf013.d.ts +21 -0
  66. package/dist/lint/post-synth/tf013.d.ts.map +1 -0
  67. package/dist/lint/post-synth/tf014.d.ts +25 -0
  68. package/dist/lint/post-synth/tf014.d.ts.map +1 -0
  69. package/dist/lint/post-synth/tf015.d.ts +21 -0
  70. package/dist/lint/post-synth/tf015.d.ts.map +1 -0
  71. package/dist/lint/post-synth/tf016.d.ts +30 -0
  72. package/dist/lint/post-synth/tf016.d.ts.map +1 -0
  73. package/dist/lint/post-synth/tf017.d.ts +19 -0
  74. package/dist/lint/post-synth/tf017.d.ts.map +1 -0
  75. package/dist/lint/post-synth/tf018.d.ts +20 -0
  76. package/dist/lint/post-synth/tf018.d.ts.map +1 -0
  77. package/dist/lint/post-synth/tf019.d.ts +25 -0
  78. package/dist/lint/post-synth/tf019.d.ts.map +1 -0
  79. package/dist/lint/post-synth/tf020.d.ts +38 -0
  80. package/dist/lint/post-synth/tf020.d.ts.map +1 -0
  81. package/dist/lint/post-synth/tf021.d.ts +41 -0
  82. package/dist/lint/post-synth/tf021.d.ts.map +1 -0
  83. package/dist/lint/post-synth/tf022.d.ts +24 -0
  84. package/dist/lint/post-synth/tf022.d.ts.map +1 -0
  85. package/dist/lint/post-synth/tf024.d.ts +24 -0
  86. package/dist/lint/post-synth/tf024.d.ts.map +1 -0
  87. package/dist/lint/post-synth/tf025.d.ts +29 -0
  88. package/dist/lint/post-synth/tf025.d.ts.map +1 -0
  89. package/dist/lint/post-synth/tf026.d.ts +21 -0
  90. package/dist/lint/post-synth/tf026.d.ts.map +1 -0
  91. package/dist/lint/rules/fixtures/load.d.ts +13 -0
  92. package/dist/lint/rules/fixtures/load.d.ts.map +1 -0
  93. package/dist/lint/rules/plan-before-apply.d.ts +11 -0
  94. package/dist/lint/rules/plan-before-apply.d.ts.map +1 -1
  95. package/dist/lint/secret-shape.d.ts +68 -0
  96. package/dist/lint/secret-shape.d.ts.map +1 -0
  97. package/dist/live-ownership.d.ts +55 -0
  98. package/dist/live-ownership.d.ts.map +1 -0
  99. package/dist/lsp/option-keys.d.ts.map +1 -1
  100. package/dist/manifest.json +1 -1
  101. package/dist/okf/index.md +24 -0
  102. package/dist/okf/rules/TF002.md +11 -0
  103. package/dist/okf/rules/TF003.md +11 -0
  104. package/dist/okf/rules/TF004.md +11 -0
  105. package/dist/okf/rules/TF005.md +11 -0
  106. package/dist/okf/rules/TF006.md +11 -0
  107. package/dist/okf/rules/TF007.md +11 -0
  108. package/dist/okf/rules/TF008.md +11 -0
  109. package/dist/okf/rules/TF009.md +11 -0
  110. package/dist/okf/rules/TF010.md +11 -0
  111. package/dist/okf/rules/TF011.md +11 -0
  112. package/dist/okf/rules/TF012.md +11 -0
  113. package/dist/okf/rules/TF013.md +11 -0
  114. package/dist/okf/rules/TF014.md +11 -0
  115. package/dist/okf/rules/TF015.md +11 -0
  116. package/dist/okf/rules/TF016.md +11 -0
  117. package/dist/okf/rules/TF017.md +11 -0
  118. package/dist/okf/rules/TF018.md +11 -0
  119. package/dist/okf/rules/TF019.md +11 -0
  120. package/dist/okf/rules/TF020.md +11 -0
  121. package/dist/okf/rules/TF021.md +11 -0
  122. package/dist/okf/rules/TF022.md +11 -0
  123. package/dist/okf/rules/TF024.md +11 -0
  124. package/dist/okf/rules/TF025.md +11 -0
  125. package/dist/okf/rules/TF026.md +11 -0
  126. package/dist/op/activities/index.d.ts +13 -6
  127. package/dist/op/activities/index.d.ts.map +1 -1
  128. package/dist/op/activities/live-detect.d.ts +51 -0
  129. package/dist/op/activities/live-detect.d.ts.map +1 -0
  130. package/dist/op/activities/terraform.d.ts +445 -17
  131. package/dist/op/activities/terraform.d.ts.map +1 -1
  132. package/dist/op/activity-contracts.d.ts +148 -0
  133. package/dist/op/activity-contracts.d.ts.map +1 -0
  134. package/dist/op/adoption.d.ts +118 -0
  135. package/dist/op/adoption.d.ts.map +1 -0
  136. package/dist/op/builders.d.ts +46 -6
  137. package/dist/op/builders.d.ts.map +1 -1
  138. package/dist/op/resolve-root-mode.d.ts +45 -0
  139. package/dist/op/resolve-root-mode.d.ts.map +1 -0
  140. package/dist/plugin.d.ts.map +1 -1
  141. package/dist/rules/blocks.ts +109 -0
  142. package/dist/rules/module-source.ts +121 -0
  143. package/dist/rules/plan-before-apply.ts +11 -0
  144. package/dist/rules/scope.ts +107 -0
  145. package/dist/rules/tf001.ts +32 -8
  146. package/dist/rules/tf002.ts +151 -0
  147. package/dist/rules/tf003.ts +71 -0
  148. package/dist/rules/tf004.ts +63 -0
  149. package/dist/rules/tf005.ts +76 -0
  150. package/dist/rules/tf006.ts +53 -0
  151. package/dist/rules/tf007.ts +78 -0
  152. package/dist/rules/tf008.ts +75 -0
  153. package/dist/rules/tf009.ts +59 -0
  154. package/dist/rules/tf010.ts +51 -0
  155. package/dist/rules/tf011.ts +49 -0
  156. package/dist/rules/tf012.ts +47 -0
  157. package/dist/rules/tf013.ts +63 -0
  158. package/dist/rules/tf014.ts +72 -0
  159. package/dist/rules/tf015.ts +63 -0
  160. package/dist/rules/tf016.ts +75 -0
  161. package/dist/rules/tf017.ts +50 -0
  162. package/dist/rules/tf018.ts +72 -0
  163. package/dist/rules/tf019.ts +72 -0
  164. package/dist/rules/tf020.ts +123 -0
  165. package/dist/rules/tf021.ts +126 -0
  166. package/dist/rules/tf022.ts +61 -0
  167. package/dist/rules/tf024.ts +70 -0
  168. package/dist/rules/tf025.ts +101 -0
  169. package/dist/rules/tf026.ts +80 -0
  170. package/dist/skills/chant-terraform.md +1 -1
  171. package/package.json +7 -2
  172. package/src/__fixtures__/live/main.tf +28 -0
  173. package/src/__fixtures__/live-adopt/estate.chdf.hcl +6 -0
  174. package/src/__fixtures__/live-adopt/main.tf +21 -0
  175. package/src/__fixtures__/live-estate/README.md +58 -0
  176. package/src/__fixtures__/live-estate/compute.tf +10 -0
  177. package/src/__fixtures__/live-estate/estate.chdf.hcl +1 -0
  178. package/src/__fixtures__/live-estate/logs.tf +8 -0
  179. package/src/__fixtures__/live-estate/network.tf +24 -0
  180. package/src/__fixtures__/live-estate/rule.tf +11 -0
  181. package/src/__fixtures__/live-estate/unowned.tf +21 -0
  182. package/src/__fixtures__/live-estate/versions.tf +31 -0
  183. package/src/__fixtures__/live-ls.json +98 -0
  184. package/src/__fixtures__/live-plan.json +285 -0
  185. package/src/__fixtures__/live-sidecar/estate.chdf.hcl +3 -0
  186. package/src/__fixtures__/live-sidecar/main.tf +24 -0
  187. package/src/__fixtures__/module-tree/README.md +16 -0
  188. package/src/__fixtures__/module-tree/cycle/main.tf +3 -0
  189. package/src/__fixtures__/module-tree/cycle/modules/a/main.tf +9 -0
  190. package/src/__fixtures__/module-tree/cycle/modules/b/main.tf +9 -0
  191. package/src/__fixtures__/module-tree/missing/main.tf +9 -0
  192. package/src/__fixtures__/module-tree/outside/root/main.tf +9 -0
  193. package/src/__fixtures__/module-tree/root/main.tf +28 -0
  194. package/src/__fixtures__/module-tree/root/modules/cdn/main.tf +15 -0
  195. package/src/__fixtures__/module-tree/root/modules/cdn/modules/bucket/main.tf +11 -0
  196. package/src/__fixtures__/module-tree/shared/main.tf +5 -0
  197. package/src/__fixtures__/references/main.tf +56 -0
  198. package/src/codegen/lint-rules-page.test.ts +28 -0
  199. package/src/composites/terraform-adopt-op.acceptance.test.ts +166 -0
  200. package/src/composites/terraform-adopt-op.test.ts +170 -0
  201. package/src/composites/terraform-adopt-op.ts +197 -0
  202. package/src/composites/terraform-apply-op.acceptance.test.ts +183 -5
  203. package/src/composites/terraform-apply-op.test.ts +167 -15
  204. package/src/composites/terraform-apply-op.ts +105 -28
  205. package/src/composites/terraform-watch-op.test.ts +85 -26
  206. package/src/composites/terraform-watch-op.ts +130 -73
  207. package/src/config.ts +32 -3
  208. package/src/describe-resources.live.test.ts +598 -0
  209. package/src/describe-resources.test.ts +53 -0
  210. package/src/describe-resources.ts +811 -28
  211. package/src/hcl/descend.test.ts +162 -0
  212. package/src/hcl/descend.ts +273 -0
  213. package/src/hcl/marker.test.ts +147 -0
  214. package/src/hcl/marker.ts +235 -0
  215. package/src/hcl/parse.test.ts +69 -0
  216. package/src/hcl/parse.ts +249 -27
  217. package/src/hcl/references.test.ts +116 -0
  218. package/src/hcl/references.ts +172 -0
  219. package/src/hcl/roots.ts +59 -5
  220. package/src/hcl/suppressions.test.ts +147 -0
  221. package/src/hcl/suppressions.ts +204 -0
  222. package/src/hcl/value.test.ts +73 -0
  223. package/src/hcl/value.ts +106 -0
  224. package/src/index.ts +31 -1
  225. package/src/lint/audit-catalog.ts +264 -3
  226. package/src/lint/audit-lineage.ts +319 -1
  227. package/src/lint/audit.test.ts +40 -1
  228. package/src/lint/post-synth/blocks.ts +109 -0
  229. package/src/lint/post-synth/fixtures/TF001/negative.tf +28 -0
  230. package/src/lint/post-synth/fixtures/TF001/positive.tf +24 -0
  231. package/src/lint/post-synth/fixtures/TF002/negative.tf +17 -0
  232. package/src/lint/post-synth/fixtures/TF002/positive-legacy.tf +10 -0
  233. package/src/lint/post-synth/fixtures/TF002/positive-no-version.tf +12 -0
  234. package/src/lint/post-synth/fixtures/TF002/positive.tf +7 -0
  235. package/src/lint/post-synth/fixtures/TF003/negative.tf +10 -0
  236. package/src/lint/post-synth/fixtures/TF003/positive.tf +8 -0
  237. package/src/lint/post-synth/fixtures/TF004/negative-git.tf +3 -0
  238. package/src/lint/post-synth/fixtures/TF004/negative-local.tf +3 -0
  239. package/src/lint/post-synth/fixtures/TF004/negative.tf +4 -0
  240. package/src/lint/post-synth/fixtures/TF004/positive.tf +3 -0
  241. package/src/lint/post-synth/fixtures/TF005/negative-local.tf +3 -0
  242. package/src/lint/post-synth/fixtures/TF005/negative-registry.tf +3 -0
  243. package/src/lint/post-synth/fixtures/TF005/negative-sha.tf +3 -0
  244. package/src/lint/post-synth/fixtures/TF005/negative.tf +3 -0
  245. package/src/lint/post-synth/fixtures/TF005/positive-branch.tf +3 -0
  246. package/src/lint/post-synth/fixtures/TF005/positive-non-semver.tf +3 -0
  247. package/src/lint/post-synth/fixtures/TF005/positive.tf +3 -0
  248. package/src/lint/post-synth/fixtures/TF006/negative.tf +5 -0
  249. package/src/lint/post-synth/fixtures/TF006/positive-empty-default.tf +11 -0
  250. package/src/lint/post-synth/fixtures/TF006/positive.tf +6 -0
  251. package/src/lint/post-synth/fixtures/TF007/negative.tf +10 -0
  252. package/src/lint/post-synth/fixtures/TF007/positive.tf +9 -0
  253. package/src/lint/post-synth/fixtures/TF008/negative-variable.tf +10 -0
  254. package/src/lint/post-synth/fixtures/TF008/negative.tf +8 -0
  255. package/src/lint/post-synth/fixtures/TF008/positive-other-provider.tf +10 -0
  256. package/src/lint/post-synth/fixtures/TF008/positive.tf +5 -0
  257. package/src/lint/post-synth/fixtures/TF009/negative.tf +10 -0
  258. package/src/lint/post-synth/fixtures/TF009/positive.tf +4 -0
  259. package/src/lint/post-synth/fixtures/TF010/negative.tf +5 -0
  260. package/src/lint/post-synth/fixtures/TF010/positive.tf +4 -0
  261. package/src/lint/post-synth/fixtures/TF011/negative.tf +5 -0
  262. package/src/lint/post-synth/fixtures/TF011/positive-blank.tf +5 -0
  263. package/src/lint/post-synth/fixtures/TF011/positive.tf +4 -0
  264. package/src/lint/post-synth/fixtures/TF012/negative.tf +4 -0
  265. package/src/lint/post-synth/fixtures/TF012/positive.tf +3 -0
  266. package/src/lint/post-synth/fixtures/TF013/negative.tf +8 -0
  267. package/src/lint/post-synth/fixtures/TF013/positive-data.tf +8 -0
  268. package/src/lint/post-synth/fixtures/TF013/positive.tf +8 -0
  269. package/src/lint/post-synth/fixtures/TF014/negative/main.tf +26 -0
  270. package/src/lint/post-synth/fixtures/TF014/negative/modules/cdn/main.tf +19 -0
  271. package/src/lint/post-synth/fixtures/TF014/positive/main.tf +16 -0
  272. package/src/lint/post-synth/fixtures/TF014/positive/modules/cdn/main.tf +15 -0
  273. package/src/lint/post-synth/fixtures/TF015/negative/main.tf +13 -0
  274. package/src/lint/post-synth/fixtures/TF015/negative/modules/cdn/main.tf +16 -0
  275. package/src/lint/post-synth/fixtures/TF015/positive/main.tf +13 -0
  276. package/src/lint/post-synth/fixtures/TF015/positive/modules/cdn/main.tf +14 -0
  277. package/src/lint/post-synth/fixtures/TF016/negative-template.tf +7 -0
  278. package/src/lint/post-synth/fixtures/TF016/negative.tf +16 -0
  279. package/src/lint/post-synth/fixtures/TF016/positive-shared-file.tf +12 -0
  280. package/src/lint/post-synth/fixtures/TF016/positive.tf +13 -0
  281. package/src/lint/post-synth/fixtures/TF017/negative.tf +4 -0
  282. package/src/lint/post-synth/fixtures/TF017/positive.tf +5 -0
  283. package/src/lint/post-synth/fixtures/TF018/negative.tf +9 -0
  284. package/src/lint/post-synth/fixtures/TF018/positive-data.tf +9 -0
  285. package/src/lint/post-synth/fixtures/TF018/positive.tf +4 -0
  286. package/src/lint/post-synth/fixtures/TF019/negative.tf +14 -0
  287. package/src/lint/post-synth/fixtures/TF019/positive-output.tf +5 -0
  288. package/src/lint/post-synth/fixtures/TF019/positive.tf +16 -0
  289. package/src/lint/post-synth/fixtures/TF020/negative.tf +43 -0
  290. package/src/lint/post-synth/fixtures/TF020/positive.tf +32 -0
  291. package/src/lint/post-synth/fixtures/TF020/scoped/main.tf +21 -0
  292. package/src/lint/post-synth/fixtures/TF020/scoped/modules/cdn/main.tf +13 -0
  293. package/src/lint/post-synth/fixtures/TF021/negative.tf +31 -0
  294. package/src/lint/post-synth/fixtures/TF021/positive-length.tf +8 -0
  295. package/src/lint/post-synth/fixtures/TF021/positive.tf +8 -0
  296. package/src/lint/post-synth/fixtures/TF022/negative-locator.tf +7 -0
  297. package/src/lint/post-synth/fixtures/TF022/negative.tf +10 -0
  298. package/src/lint/post-synth/fixtures/TF022/positive-nested.tf +10 -0
  299. package/src/lint/post-synth/fixtures/TF022/positive.tf +6 -0
  300. package/src/lint/post-synth/fixtures/TF023/negative/main.tf +15 -0
  301. package/src/lint/post-synth/fixtures/TF023/positive/main.tf +9 -0
  302. package/src/lint/post-synth/fixtures/TF023/positive/state.json +24 -0
  303. package/src/lint/post-synth/fixtures/TF024/live-clean/main.tf +9 -0
  304. package/src/lint/post-synth/fixtures/TF024/live-with-backend/main.tf +14 -0
  305. package/src/lint/post-synth/fixtures/TF024/live-with-cloud/main.tf +16 -0
  306. package/src/lint/post-synth/fixtures/TF025/live-clean/main.tf +13 -0
  307. package/src/lint/post-synth/fixtures/TF025/live-with-workspace-ref/main.tf +13 -0
  308. package/src/lint/post-synth/fixtures/TF026/live-default/main.tf +9 -0
  309. package/src/lint/post-synth/fixtures/TF026/live-explicit-delete/main.tf +13 -0
  310. package/src/lint/post-synth/fixtures/TF026/live-keep/main.tf +13 -0
  311. package/src/lint/post-synth/fixtures/load.ts +108 -0
  312. package/src/lint/post-synth/index.ts +48 -0
  313. package/src/lint/post-synth/module-rules.test.ts +153 -0
  314. package/src/lint/post-synth/module-source.test.ts +75 -0
  315. package/src/lint/post-synth/module-source.ts +121 -0
  316. package/src/lint/post-synth/post-synth.test.ts +14 -15
  317. package/src/lint/post-synth/scope.ts +107 -0
  318. package/src/lint/post-synth/single-block.test.ts +263 -0
  319. package/src/lint/post-synth/suppression.test.ts +110 -0
  320. package/src/lint/post-synth/tf001.ts +32 -8
  321. package/src/lint/post-synth/tf002.test.ts +93 -0
  322. package/src/lint/post-synth/tf002.ts +151 -0
  323. package/src/lint/post-synth/tf003.test.ts +75 -0
  324. package/src/lint/post-synth/tf003.ts +71 -0
  325. package/src/lint/post-synth/tf004.test.ts +47 -0
  326. package/src/lint/post-synth/tf004.ts +63 -0
  327. package/src/lint/post-synth/tf005.test.ts +57 -0
  328. package/src/lint/post-synth/tf005.ts +76 -0
  329. package/src/lint/post-synth/tf006.ts +53 -0
  330. package/src/lint/post-synth/tf007.ts +78 -0
  331. package/src/lint/post-synth/tf008.ts +75 -0
  332. package/src/lint/post-synth/tf009.ts +59 -0
  333. package/src/lint/post-synth/tf010.ts +51 -0
  334. package/src/lint/post-synth/tf011.ts +49 -0
  335. package/src/lint/post-synth/tf012.ts +47 -0
  336. package/src/lint/post-synth/tf013.ts +63 -0
  337. package/src/lint/post-synth/tf014.ts +72 -0
  338. package/src/lint/post-synth/tf015.ts +63 -0
  339. package/src/lint/post-synth/tf016.ts +75 -0
  340. package/src/lint/post-synth/tf017.ts +50 -0
  341. package/src/lint/post-synth/tf018.ts +72 -0
  342. package/src/lint/post-synth/tf019.ts +72 -0
  343. package/src/lint/post-synth/tf020.ts +123 -0
  344. package/src/lint/post-synth/tf021.ts +126 -0
  345. package/src/lint/post-synth/tf022.ts +61 -0
  346. package/src/lint/post-synth/tf024.test.ts +42 -0
  347. package/src/lint/post-synth/tf024.ts +70 -0
  348. package/src/lint/post-synth/tf025.test.ts +46 -0
  349. package/src/lint/post-synth/tf025.ts +101 -0
  350. package/src/lint/post-synth/tf026.test.ts +56 -0
  351. package/src/lint/post-synth/tf026.ts +80 -0
  352. package/src/lint/presets.test.ts +72 -0
  353. package/src/lint/rules/fixtures/TF101/negative-op.ts +2 -0
  354. package/src/lint/rules/fixtures/TF101/positive-op.ts +2 -0
  355. package/src/lint/rules/fixtures/load.ts +53 -0
  356. package/src/lint/rules/plan-before-apply.test.ts +112 -7
  357. package/src/lint/rules/plan-before-apply.ts +11 -0
  358. package/src/lint/secret-shape.test.ts +150 -0
  359. package/src/lint/secret-shape.ts +159 -0
  360. package/src/live-ownership.ts +60 -0
  361. package/src/lsp/completions.test.ts +3 -2
  362. package/src/lsp/option-keys.ts +10 -5
  363. package/src/op/activities/choudoufu.acceptance.test.ts +143 -0
  364. package/src/op/activities/choudoufu.test.ts +693 -0
  365. package/src/op/activities/index.ts +40 -4
  366. package/src/op/activities/live-detect.test.ts +143 -0
  367. package/src/op/activities/live-detect.ts +162 -0
  368. package/src/op/activities/registry.test.ts +11 -3
  369. package/src/op/activities/terraform.test.ts +7 -3
  370. package/src/op/activities/terraform.ts +823 -69
  371. package/src/op/activity-contracts.test.ts +147 -0
  372. package/src/op/activity-contracts.ts +178 -0
  373. package/src/op/adoption.test.ts +170 -0
  374. package/src/op/adoption.ts +241 -0
  375. package/src/op/builders.test.ts +60 -5
  376. package/src/op/builders.ts +93 -5
  377. package/src/op/resolve-root-mode.test.ts +85 -0
  378. package/src/op/resolve-root-mode.ts +72 -0
  379. package/src/plugin.test.ts +68 -0
  380. package/src/plugin.ts +75 -9
  381. package/src/skills/chant-terraform.md +1 -1
@@ -0,0 +1,73 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { attr } from "./value";
3
+ import type { BlockBody } from "./parse";
4
+
5
+ describe("attr(): the value envelope over a parsed block body (chant #2113)", () => {
6
+ test("absent: the key is not present at all", () => {
7
+ const body: BlockBody = {};
8
+ expect(attr(body, "bucket")).toEqual({ kind: "absent", raw: undefined });
9
+ });
10
+
11
+ test("absent: null is treated the same as a missing key", () => {
12
+ const body: BlockBody = { bucket: null };
13
+ expect(attr(body, "bucket")).toEqual({ kind: "absent", raw: null });
14
+ });
15
+
16
+ test("literal: a plain string with no interpolation", () => {
17
+ const body: BlockBody = { bucket: "my-bucket" };
18
+ expect(attr(body, "bucket")).toEqual({ kind: "literal", value: "my-bucket", raw: "my-bucket" });
19
+ });
20
+
21
+ test("literal: a number", () => {
22
+ const body: BlockBody = { count: 3 };
23
+ expect(attr(body, "count")).toEqual({ kind: "literal", value: 3, raw: 3 });
24
+ });
25
+
26
+ test("literal: a boolean", () => {
27
+ const body: BlockBody = { enabled: false };
28
+ expect(attr(body, "enabled")).toEqual({ kind: "literal", value: false, raw: false });
29
+ });
30
+
31
+ test("literal: an array (also how hcl2json encodes a nested block under the same key)", () => {
32
+ const body: BlockBody = { backend: [{ s3: [{ bucket: "tfstate" }] }] };
33
+ const a = attr(body, "backend");
34
+ expect(a.kind).toBe("literal");
35
+ expect(a.value).toEqual([{ s3: [{ bucket: "tfstate" }] }]);
36
+ expect(a.raw).toBe(a.value);
37
+ });
38
+
39
+ test("literal: an object", () => {
40
+ const body: BlockBody = { tags: { team: "infra" } };
41
+ expect(attr(body, "tags")).toEqual({ kind: "literal", value: { team: "infra" }, raw: { team: "infra" } });
42
+ });
43
+
44
+ test("reference: the whole string is one interpolation", () => {
45
+ const body: BlockBody = { bucket: "${aws_s3_bucket.foo.id}" };
46
+ expect(attr(body, "bucket")).toEqual({ kind: "reference", refs: ["aws_s3_bucket.foo.id"], raw: "${aws_s3_bucket.foo.id}" });
47
+ });
48
+
49
+ test("reference: a bare variable reference", () => {
50
+ const body: BlockBody = { region: "${var.region}" };
51
+ expect(attr(body, "region")).toEqual({ kind: "reference", refs: ["var.region"], raw: "${var.region}" });
52
+ });
53
+
54
+ test("reference: a function call still counts as one whole-string reference", () => {
55
+ const body: BlockBody = { name: "${lower(var.name)}" };
56
+ expect(attr(body, "name")).toEqual({ kind: "reference", refs: ["lower(var.name)"], raw: "${lower(var.name)}" });
57
+ });
58
+
59
+ test("template: interpolation mixed with literal text", () => {
60
+ const body: BlockBody = { name: "prefix-${var.env}-suffix" };
61
+ expect(attr(body, "name")).toEqual({ kind: "template", refs: ["var.env"], raw: "prefix-${var.env}-suffix" });
62
+ });
63
+
64
+ test("template: two interpolations in one string, both captured in source order", () => {
65
+ const body: BlockBody = { name: "${var.a}-${var.b}" };
66
+ expect(attr(body, "name")).toEqual({ kind: "template", refs: ["var.a", "var.b"], raw: "${var.a}-${var.b}" });
67
+ });
68
+
69
+ test("literal: a string that merely contains a bare $ with no interpolation", () => {
70
+ const body: BlockBody = { price: "$5" };
71
+ expect(attr(body, "price")).toEqual({ kind: "literal", value: "$5", raw: "$5" });
72
+ });
73
+ });
@@ -0,0 +1,106 @@
1
+ /**
2
+ * A value envelope over a parsed block body (chant #2113).
3
+ *
4
+ * Every TF post-synth check reads `BlockBody` (`./parse.ts`), raw
5
+ * `@cdktf/hcl2json` output, and re-derives, by hand, whether an attribute is
6
+ * a plain literal, a reference/expression, or absent. tflint's OPA ruleset
7
+ * hands a policy `{value, unknown, sensitive, ephemeral, range}` per
8
+ * attribute so a check reads that uniformly instead of re-deriving it
9
+ * (`tflint-ruleset-opa`'s `docs/functions.md`, cited in the #2107 survey).
10
+ * `attr()` is the equivalent for chant's static string-based body.
11
+ *
12
+ * The shape here is narrower than tflint's for one structural reason.
13
+ * hcl2json converts HCL to plain JSON with no evaluation and no position
14
+ * data. There is no live Terraform graph behind this parse, so there is no
15
+ * `unknown` (this runs over source, not a plan), and no `sensitive`/
16
+ * `ephemeral` flag (those live on a `variable` block's own attributes, not on
17
+ * every attribute that happens to reference one; a check that cares reads
18
+ * the referenced variable's declaration itself). `range` is the one field
19
+ * tflint's envelope carries that this cannot. hcl2json's own output is bare
20
+ * values, JSON-round-tripped from HCL, with no line/column/byte-range
21
+ * attached anywhere in the tree, so there is nothing for `attr()` to read a
22
+ * position from. (#2111 adds a `props.line` to the *entity*, from a separate
23
+ * text-based mechanism this file does not touch and is not a substitute for
24
+ * per-attribute ranges. See that issue for how it gets a line at all.)
25
+ */
26
+
27
+ import type { BlockBody } from "./parse";
28
+
29
+ /** What kind of value an attribute holds, once template interpolation is accounted for. */
30
+ export type AttrKind = "literal" | "reference" | "template" | "absent";
31
+
32
+ /**
33
+ * The value envelope `attr()` returns for one attribute of a block body.
34
+ *
35
+ * `raw` is always the untouched `body[name]` (or `undefined` when absent),
36
+ * so a caller that needs the original hcl2json value for a case this
37
+ * envelope doesn't model can still reach it without a second lookup.
38
+ */
39
+ export interface AttrValue {
40
+ kind: AttrKind;
41
+ /**
42
+ * The attribute's value when `kind` is `"literal"`: a string, number,
43
+ * boolean, array or object, verbatim from hcl2json. Absent for every other
44
+ * kind (a reference/template's "value" is not known statically).
45
+ */
46
+ value?: unknown;
47
+ /**
48
+ * The expression(s) found inside `${...}` for `"reference"` and
49
+ * `"template"`: one entry for `"reference"` (the whole interpolation),
50
+ * one per `${...}` span in source order for `"template"`. Absent for
51
+ * `"literal"`/`"absent"`.
52
+ */
53
+ refs?: string[];
54
+ /** The untouched `body[name]`, regardless of `kind`. `undefined` when absent. */
55
+ raw: unknown;
56
+ }
57
+
58
+ /**
59
+ * Every `${...}` span within a string, in source order. One level of nested
60
+ * `{...}` is tolerated (an object constructor or a map-typed function argument
61
+ * inside the interpolation), which is as far as a regex can go without
62
+ * becoming a brace-counting parser. `attr()` below decides `"reference"` vs
63
+ * `"template"` from how many spans this finds and whether the one span (if
64
+ * there is exactly one) consumes the whole string, not from a second,
65
+ * separately-anchored pattern, so the two checks can't disagree about what
66
+ * counts as an interpolation.
67
+ */
68
+ const INTERPOLATION_SPANS = /\$\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g;
69
+
70
+ /**
71
+ * Read one attribute of a parsed block body as a value envelope.
72
+ *
73
+ * - `"absent"`: `name` is not a key of `body` at all (or is `undefined`/`null`).
74
+ * - `"reference"`: the value is a string that is nothing but one
75
+ * `${...}` interpolation (`"${aws_s3_bucket.foo.id}"`, `"${var.x}"`,
76
+ * `"${lower(var.x)}"`; a function call counts, since the whole value is
77
+ * still one expression, not a literal). `refs` carries that one expression.
78
+ * - `"template"`: the value is a string with `${...}` interpolation mixed
79
+ * with literal text (`"prefix-${var.x}-suffix"`, or two interpolations in
80
+ * one string). `refs` carries every span found, in source order.
81
+ * - `"literal"`: anything else. A plain string with no interpolation, a
82
+ * number, a boolean, an array, or an object (which is how hcl2json encodes
83
+ * a nested block under the same key a caller might ask `attr()` about, so
84
+ * a nested block reads as `kind: "literal"` with `value` holding its body
85
+ * or bodies array). See `hasBlock()` in `../lint/post-synth/tf001.ts`,
86
+ * which is exactly that case.
87
+ *
88
+ * A malformed `${` with no matching `}` is not specially detected; it falls
89
+ * through to `"literal"` (no complete span found), matching hcl2json's own
90
+ * behavior of passing such text through as a plain string.
91
+ */
92
+ export function attr(body: BlockBody, name: string): AttrValue {
93
+ const raw = body[name];
94
+ if (raw === undefined || raw === null) return { kind: "absent", raw };
95
+
96
+ if (typeof raw === "string") {
97
+ const spans = [...raw.matchAll(INTERPOLATION_SPANS)];
98
+ if (spans.length === 0) return { kind: "literal", value: raw, raw };
99
+
100
+ const refs = spans.map((m) => m[1].trim());
101
+ const isWholeString = spans.length === 1 && spans[0].index === 0 && spans[0][0].length === raw.length;
102
+ return isWholeString ? { kind: "reference", refs, raw } : { kind: "template", refs, raw };
103
+ }
104
+
105
+ return { kind: "literal", value: raw, raw };
106
+ }
package/src/index.ts CHANGED
@@ -21,9 +21,12 @@ export {
21
21
  blocksToEntities,
22
22
  parseTerraformRootContent,
23
23
  parseTerraformRootDir,
24
+ readLiveSidecarFile,
24
25
  splitBundleContent,
25
26
  terraformEntity,
26
27
  DATA_TYPE,
28
+ LIVE_SIDECAR_FILENAME,
29
+ LIVE_TYPE,
27
30
  LOCALS_TYPE,
28
31
  MODULE_TYPE,
29
32
  OUTPUT_TYPE,
@@ -34,6 +37,8 @@ export {
34
37
  type BlockBody,
35
38
  type TerraformEntity,
36
39
  type TerraformFile,
40
+ type TerraformRootMode,
41
+ type TerraformRootModeOptions,
37
42
  } from "./hcl/parse";
38
43
 
39
44
  export { renderTerraformRoots, type TerraformRootsResult } from "./hcl/roots";
@@ -41,7 +46,27 @@ export { renderTerraformRoots, type TerraformRootsResult } from "./hcl/roots";
41
46
  // Typed Op step builders (#2086). Same names as the activities they wrap, and
42
47
  // like k3s's they are opt-in from the lexicon root — the activities themselves
43
48
  // are reached by the registry through `./op/activities`, never from here.
44
- export { terraformInit, terraformPlan, terraformApply, terraformShow } from "./op/builders";
49
+ export {
50
+ terraformInit,
51
+ terraformPlan,
52
+ terraformApply,
53
+ terraformShow,
54
+ choudoufuLivePlan,
55
+ choudoufuLiveLs,
56
+ choudoufuLiveCheck,
57
+ choudoufuAdopt,
58
+ } from "./op/builders";
59
+
60
+ // The adoption ledger over `live-plan -json`'s document (#2105). Pure, and
61
+ // exported because the ledger's shape is what a project reading an Op's own
62
+ // result is reading.
63
+ export {
64
+ parseAdoptionCommands,
65
+ readAdoptionLedger,
66
+ renderAdoptionLedger,
67
+ type AdoptionCandidate,
68
+ type AdoptionLedger,
69
+ } from "./op/adoption";
45
70
 
46
71
  // The Init/Plan/Gate/Apply composite (#2086).
47
72
  export { TerraformApplyOp } from "./composites/terraform-apply-op";
@@ -59,6 +84,11 @@ export type {
59
84
  TerraformFindingMode,
60
85
  } from "./composites/terraform-watch-op";
61
86
 
87
+ // The Check/Ledger/Gate/Adopt composite (#2105): reconcile for a choudoufu
88
+ // estate, and #2089's answer for this backend.
89
+ export { TerraformAdoptOp } from "./composites/terraform-adopt-op";
90
+ export type { TerraformAdoptOpConfig, TerraformAdoptOpResources } from "./composites/terraform-adopt-op";
91
+
62
92
  // Live observation over `terraform show -json` (#2087). The plugin reaches
63
93
  // `describeResources` through a dynamic import; the ownership keys are here
64
94
  // because `ownershipChannel` declares them eagerly.
@@ -4,13 +4,25 @@
4
4
  * Every post-synth check needs an entry or it contributes nothing to
5
5
  * `chant audit`, silently, and `packages/core/src/audit/catalog.test.ts` fails.
6
6
  *
7
- * TF001 reads the chant model (`ctx.entities`), never emitted output, so
8
- * `yamlBased` is false. Prior-art lineage lives in ./audit-lineage.ts.
7
+ * TF001, TF024, TF025 and TF026 all read the chant model (`ctx.entities`),
8
+ * never emitted output, so `yamlBased` is false for all four. Prior-art
9
+ * lineage lives in ./audit-lineage.ts.
9
10
  */
10
- import type { RuleMeta } from "@intentius/chant/audit/catalog";
11
+ import type { Authority, RuleMeta } from "@intentius/chant/audit/catalog";
11
12
  import { applyLineage } from "@intentius/chant/audit/catalog";
12
13
  import { terraformAuditLineage } from "./audit-lineage";
13
14
 
15
+ const HASHICORP_STYLE_VARIABLES: Authority = {
16
+ name: "HashiCorp Terraform style guide — Variables",
17
+ url: "https://developer.hashicorp.com/terraform/language/style#variables",
18
+ };
19
+
20
+ // The vendor guides behind TF014, TF015 and TF021 are credited as lineage in
21
+ // ./audit-lineage.ts, not as `authority` here. `authority` is reserved for
22
+ // security rules that fail a merge (packages/core/src/audit/catalog.test.ts
23
+ // holds both halves of that invariant), and these three are correctness and
24
+ // best-practice rules (#2112).
25
+
14
26
  export const terraformAuditCatalog: Record<string, RuleMeta> = {
15
27
  TF001: {
16
28
  id: "TF001",
@@ -23,6 +35,255 @@ export const terraformAuditCatalog: Record<string, RuleMeta> = {
23
35
  "module's terraform block, then `terraform init -migrate-state`.",
24
36
  yamlBased: false,
25
37
  },
38
+ TF024: {
39
+ id: "TF024",
40
+ tier: "merge-worthy",
41
+ fixKind: "guidance",
42
+ category: "correctness",
43
+ title: "Live root declares a backend or cloud block, which choudoufu refuses",
44
+ remediation:
45
+ "Remove the `backend`/`cloud` block from the live root's terraform block; a live root's prior " +
46
+ "state is a projection rebuilt from the live system every run, so there is no state to store.",
47
+ yamlBased: false,
48
+ },
49
+ TF025: {
50
+ id: "TF025",
51
+ tier: "merge-worthy",
52
+ fixKind: "guidance",
53
+ category: "correctness",
54
+ title: "Live root references a non-default terraform.workspace, which choudoufu refuses",
55
+ remediation:
56
+ "Remove `workspace` from the root's terraform.roots config, and remove any `terraform.workspace` " +
57
+ 'reference from its HCL; choudoufu refuses any workspace but "default" on a live root.',
58
+ yamlBased: false,
59
+ },
60
+ TF026: {
61
+ id: "TF026",
62
+ tier: "merge-worthy",
63
+ fixKind: "guidance",
64
+ category: "correctness",
65
+ title: 'Live root declares delete: "never" but its policy leaves undeclared_tagged at "delete"',
66
+ remediation:
67
+ 'Add undeclared_tagged = "keep" (or "untag" or "report") to the live root\'s policy block, or ' +
68
+ 'change terraform.roots.<name>.delete to "owned-only" or "gated" if an owned orphan should be ' +
69
+ "deleted after all.",
70
+ yamlBased: false,
71
+ },
72
+ TF002: {
73
+ id: "TF002",
74
+ tier: "merge-worthy",
75
+ fixKind: "guidance",
76
+ category: "correctness",
77
+ title: "Provider implied by the root has no required_providers entry",
78
+ remediation:
79
+ "Add the provider to the terraform block's `required_providers`, with both `source` and " +
80
+ "`version` set, so a future provider release can't silently change behavior.",
81
+ yamlBased: false,
82
+ },
83
+ TF003: {
84
+ id: "TF003",
85
+ tier: "merge-worthy",
86
+ fixKind: "guidance",
87
+ category: "correctness",
88
+ title: "Root module's terraform block has no required_version",
89
+ remediation: 'Set `required_version = ">= <lowest supported version>"` in the terraform block.',
90
+ yamlBased: false,
91
+ },
92
+ TF004: {
93
+ id: "TF004",
94
+ tier: "merge-worthy",
95
+ fixKind: "guidance",
96
+ category: "correctness",
97
+ title: "Registry-sourced module block has no version",
98
+ remediation: "Add a `version` constraint to the module block (e.g. `~> 5.0`).",
99
+ yamlBased: false,
100
+ },
101
+ TF005: {
102
+ id: "TF005",
103
+ tier: "merge-worthy",
104
+ fixKind: "guidance",
105
+ category: "correctness",
106
+ title: "Git/hg module source is unpinned, or pinned to a mutable ref",
107
+ remediation: "Pin the module's `?ref=` to a tag or a full commit SHA.",
108
+ yamlBased: false,
109
+ },
110
+ TF006: {
111
+ id: "TF006",
112
+ tier: "merge-worthy",
113
+ fixKind: "guidance",
114
+ category: "security",
115
+ title: "Default value on a sensitive variable",
116
+ remediation:
117
+ "Delete the `default` and require the caller to supply the value at apply time, through a tfvars " +
118
+ "file kept out of version control, a `TF_VAR_` entry in the environment, or a workspace input.",
119
+ yamlBased: false,
120
+ },
121
+ TF007: {
122
+ id: "TF007",
123
+ tier: "merge-worthy",
124
+ fixKind: "guidance",
125
+ category: "security",
126
+ title: "Plaintext secret in a variable default or a locals value",
127
+ remediation:
128
+ "Remove the literal, mark the variable `sensitive = true`, and read the value from a secret " +
129
+ "manager data source or an input at apply time, then rotate the credential, which is already " +
130
+ "in version control history.",
131
+ yamlBased: false,
132
+ },
133
+ TF008: {
134
+ id: "TF008",
135
+ tier: "merge-worthy",
136
+ fixKind: "guidance",
137
+ category: "security",
138
+ title: "Provider block configures a hardcoded credential",
139
+ remediation:
140
+ "Delete the credential from the provider block and let the provider read it from the " +
141
+ "environment, a shared credentials file, or an OIDC role assumption. Rotate the credential.",
142
+ yamlBased: false,
143
+ },
144
+ TF009: {
145
+ id: "TF009",
146
+ tier: "merge-worthy",
147
+ fixKind: "guidance",
148
+ category: "security",
149
+ title: "Credential-named variable is not marked sensitive",
150
+ remediation:
151
+ "Add `sensitive = true` to the variable so its value is not printed in plan and apply output. " +
152
+ "The value is still stored in plaintext in state, so keep the state remote and encrypted.",
153
+ authority: [HASHICORP_STYLE_VARIABLES],
154
+ yamlBased: false,
155
+ },
156
+ TF010: {
157
+ id: "TF010",
158
+ tier: "report-only",
159
+ fixKind: "guidance",
160
+ category: "best-practice",
161
+ title: "Variable declares no type constraint",
162
+ remediation: "Add a `type` to the variable (`string`, `number`, `bool`, `list(string)`, `object({...})`).",
163
+ yamlBased: false,
164
+ },
165
+ TF011: {
166
+ id: "TF011",
167
+ tier: "report-only",
168
+ fixKind: "guidance",
169
+ category: "best-practice",
170
+ title: "Undocumented variable",
171
+ remediation: "Add a `description` saying what the value is for and what a valid one looks like.",
172
+ yamlBased: false,
173
+ },
174
+ TF012: {
175
+ id: "TF012",
176
+ tier: "report-only",
177
+ fixKind: "guidance",
178
+ category: "best-practice",
179
+ title: "Undocumented output",
180
+ remediation: "Write a `description` for the output, covering what the value is and what a caller can rely on it for.",
181
+ yamlBased: false,
182
+ },
183
+ TF013: {
184
+ id: "TF013",
185
+ tier: "merge-worthy",
186
+ fixKind: "guidance",
187
+ category: "correctness",
188
+ title: "lifecycle ignore_changes is set to all",
189
+ remediation:
190
+ "Replace `ignore_changes = all` with the list of attributes that genuinely change outside " +
191
+ "Terraform, so every other attribute is still reconciled.",
192
+ yamlBased: false,
193
+ },
194
+ TF014: {
195
+ id: "TF014",
196
+ tier: "merge-worthy",
197
+ fixKind: "guidance",
198
+ category: "correctness",
199
+ title: "Child module configures a provider block",
200
+ remediation:
201
+ "Move the provider configuration to the root module and pass it into the module with " +
202
+ "`providers = { ... }`, leaving at most an `alias`-only block in the module.",
203
+ yamlBased: false,
204
+ },
205
+ TF015: {
206
+ id: "TF015",
207
+ tier: "merge-worthy",
208
+ fixKind: "guidance",
209
+ category: "correctness",
210
+ title: "Child module declares a backend or cloud block",
211
+ remediation:
212
+ "Delete the `backend`/`cloud` block from the child module. State belongs to the root module " +
213
+ "that calls it, one state for the whole tree.",
214
+ yamlBased: false,
215
+ },
216
+ TF020: {
217
+ id: "TF020",
218
+ tier: "report-only",
219
+ fixKind: "guidance",
220
+ category: "best-practice",
221
+ title: "Declaration is never referenced in its module scope",
222
+ remediation:
223
+ "Delete the variable, local, data source or aliased provider, or reference it where it was " +
224
+ "meant to be used.",
225
+ yamlBased: false,
226
+ },
227
+ TF021: {
228
+ id: "TF021",
229
+ tier: "report-only",
230
+ fixKind: "guidance",
231
+ category: "best-practice",
232
+ title: "count builds instance identities from count.index where for_each is safer",
233
+ remediation:
234
+ "Switch to `for_each` over a map or set so each instance is addressed by a stable key, then " +
235
+ "`terraform state mv` the existing indexed instances onto their new keys.",
236
+ yamlBased: false,
237
+ },
238
+ TF016: {
239
+ id: "TF016",
240
+ tier: "report-only",
241
+ fixKind: "deterministic",
242
+ category: "best-practice",
243
+ title: "Attribute value is a quoted interpolation of a single expression",
244
+ remediation: 'Drop the quotes and the `${}`: write `x = var.y`, not `x = "${var.y}"`.',
245
+ yamlBased: false,
246
+ },
247
+ TF017: {
248
+ id: "TF017",
249
+ tier: "report-only",
250
+ fixKind: "guidance",
251
+ category: "best-practice",
252
+ title: "Module block uses depends_on",
253
+ remediation:
254
+ "Remove `depends_on` from the module call and pass an attribute of the dependency into a module " +
255
+ "input, so Terraform derives the narrower edge itself.",
256
+ yamlBased: false,
257
+ },
258
+ TF018: {
259
+ id: "TF018",
260
+ tier: "report-only",
261
+ fixKind: "guidance",
262
+ category: "best-practice",
263
+ title: "Output value is a whole resource or data source",
264
+ remediation: "Return the attribute the caller needs (`.id`, `.arn`, `.endpoint`) instead of the whole block.",
265
+ yamlBased: false,
266
+ },
267
+ TF019: {
268
+ id: "TF019",
269
+ tier: "report-only",
270
+ fixKind: "deterministic",
271
+ category: "best-practice",
272
+ title: "Meta-argument explicitly set to its default of false",
273
+ remediation: "Deleting the line is the whole fix. `sensitive`, `ephemeral`, `prevent_destroy` and `create_before_destroy` are false unless set.",
274
+ yamlBased: false,
275
+ },
276
+ TF022: {
277
+ id: "TF022",
278
+ tier: "merge-worthy",
279
+ fixKind: "guidance",
280
+ category: "security",
281
+ title: "Credential-named resource attribute holds a plaintext literal",
282
+ remediation:
283
+ "Replace the constant with a lookup, either an input the caller supplies or a data source " +
284
+ "pointed at whatever vault owns the credential, and rotate what was committed.",
285
+ yamlBased: false,
286
+ },
26
287
  };
27
288
 
28
289
  // Prior art credits, if any, live beside the rules in ./audit-lineage.ts (see