@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
@@ -15,7 +15,325 @@
15
15
  * `terraform_workspace_remote` (a `terraform.workspace` compatibility check
16
16
  * against remote execution, a different condition than "no backend is
17
17
  * configured at all"). Ship with no lineage rather than invent a credit.
18
+ *
19
+ * TF101 (plan-before-apply, `lint/rules/plan-before-apply.ts`) is a source-level
20
+ * lint rule over chant's own Op builders, not a post-synth check, so it carries
21
+ * no `RuleMeta`/`Lineage` entry here at all: this file's `Record` only ever
22
+ * keys post-synth check ids. Its own page section (docs/pages/lint-rules.mdx)
23
+ * says the same thing directly: no tool anywhere models "this plan file traces
24
+ * back to a specific preceding plan step", because that is a question about
25
+ * chant's own DSL, not about a Terraform root.
26
+ *
27
+ * ## The survey registry
28
+ *
29
+ * #2107 ran three research passes over every serious Terraform lint, scan and
30
+ * policy tool (1,888 lines, 222 cited URLs, attached to that issue as
31
+ * comments). The tools and documents below are what the survey found worth
32
+ * crediting from across that research. #2108 added none of them to
33
+ * `PRIOR_ART`: `packages/core/src/audit/catalog.test.ts`'s "every registered
34
+ * tool is credited" test fails on an entry no shipped rule cites yet, and
35
+ * neither TF001 nor TF101 cites anything. #2109 (TF002-TF005) is the first to
36
+ * draw from this list: `tflint-ruleset-terraform`, `terraform-sentinel-policies`,
37
+ * `hashicorp-style-guide` and `aws-terraform-prescriptive-guidance` are now in
38
+ * `PRIOR_ART`, spelled exactly as below. `checkov` and `kics` were already
39
+ * registered (other lexicons cite them); TF004 and TF005 are the first
40
+ * Terraform rules to. Each later rule issue adds the remaining entries it
41
+ * needs the first time a shipped rule cites them: #2112 (TF014, TF015, TF020,
42
+ * TF021) added `gcp-terraform-best-practices`, the last row of the table below
43
+ * that no shipped rule had cited yet.
44
+ *
45
+ * | Key | Name | URL | Licence | Kind | Note |
46
+ * |---|---|---|---|---|---|
47
+ * | `tflint` | tflint | https://github.com/terraform-linters/tflint | MPL-2.0 (its `terraform` package is BUSL-1.1; binaries are bound by both) | scanner | The plugin host; ships no language rules of its own. |
48
+ * | `tflint-ruleset-terraform` | tflint-ruleset-terraform | https://github.com/terraform-linters/tflint-ruleset-terraform | MPL-2.0 | scanner | The official language-level ruleset, bundled into tflint. |
49
+ * | `tflint-ruleset-redeploy` | tflint-ruleset-redeploy | https://github.com/RedeployAB/tflint-ruleset-redeploy | 0BSD | scanner | Community ruleset; richest source of language-level rules outside the official set. |
50
+ * | `tflint-ruleset-avm` | tflint-ruleset-avm | https://github.com/Azure/tflint-ruleset-avm | MIT | scanner | Azure Verified Modules conformance; two of its 33 rules are provider-agnostic. |
51
+ * | `tfsec` | tfsec | https://github.com/aquasecurity/tfsec | MIT | scanner | Historical: not archived but effectively frozen (no release since 2025-05); its docs site 404s above v0.61.x, so cite pinned repository paths (`github.com/aquasecurity/tfsec/blob/master/docs/checks/...`), not the docs site. |
52
+ * | `trivy-checks` | trivy-checks | https://github.com/aquasecurity/trivy-checks | MIT (not the trivy scanner's own Apache-2.0; cite the checks repo's licence, not the engine's) | scanner | Where tfsec's rules live today (tfsec -> defsec -> trivy + trivy-checks). Cite rules by `long_id` (stable, human-readable); `AVD-*` ids are aliases now. |
53
+ * | `terraform-sentinel-policies` | HashiCorp reference Sentinel policies | https://github.com/hashicorp/terraform-sentinel-policies | MPL-2.0 | specification | HashiCorp's own example policy set; ships zero rules baked into HCP Terraform itself, so treat a cited policy as a documented pattern (`specification`) unless a future entry runs it as a real check (`scanner`); the `kind` union allows either. |
54
+ * | `choudoufu` | choudoufu | https://github.com/INTENTIUS/choudoufu | MPL-2.0 | scanner | Its `internal/live/lint` package; experimental, AWS-only. |
55
+ * | `hashicorp-style-guide` | HashiCorp Terraform style guide | https://developer.hashicorp.com/terraform/language/style | n/a | specification | |
56
+ * | `gcp-terraform-best-practices` | Google Cloud Terraform best-practices series | https://docs.cloud.google.com/docs/terraform/best-practices/general-style-structure | n/a | specification | A multi-page series; cite the specific page's fragment. |
57
+ * | `aws-terraform-prescriptive-guidance` | AWS prescriptive guidance for Terraform | https://docs.aws.amazon.com/prescriptive-guidance/latest/terraform-aws-provider-best-practices/ | n/a | specification | |
58
+ *
59
+ * Deliberately excluded, per the survey: Terrascan and regula (both archived,
60
+ * do not build on dead projects); Snyk IaC (a product, not a rule source);
61
+ * semgrep (no SPDX id: its engine is LGPL-2.1 but its rules ship under the
62
+ * proprietary Semgrep Rules License v1.0, which forbids redistribution; cite
63
+ * it by name in prose if a rule's idea comes from it, never register it in
64
+ * `PRIOR_ART` with an SPDX licence).
65
+ *
66
+ * TF024 (#2103, live root declares a backend/cloud block) credits choudoufu's
67
+ * own `RuleStateBackend`, `internal/live/lint/issue.go` of
68
+ * github.com/INTENTIUS/choudoufu: the identical condition, checked the same
69
+ * way this fork checks it at `init` before any command runs. TF025 (a live
70
+ * root's non-default `terraform.workspace`) has no entry: its workspace half
71
+ * is refused at the CLI argument-parsing layer there
72
+ * (`internal/command/live_mode.go`), not by a dedicated lint rule under
73
+ * `internal/live/lint`, so there is no `Rule*` constant to point at.
74
+ *
75
+ * TF026 (#2106, a live root's `delete: "never"` against its `policy` block's
76
+ * `undeclared_tagged` verb) has no entry either, the same reason TF001 has
77
+ * none: choudoufu's own `internal/live/lint` package checks a policy block
78
+ * for internal validity (`RulePolicyVerb`: an unrecognized or wrong-quadrant
79
+ * verb; `RulePolicyScope`: `undeclared_untagged = "delete"` with no `scope`
80
+ * block), never against a `delete: "never" | "owned-only" | "gated"`
81
+ * classification, because choudoufu has no such classification — that
82
+ * vocabulary is chant's own, shared across lexicons (`lexicons/k8s/src/op/
83
+ * activities/kubectl.ts`'s `ApplyDeleteMode`, `packages/core/src/op/
84
+ * activities/apply.ts`'s `DeleteMode`), applied here to a tool that does not
85
+ * itself carry the concept. Ship with no lineage rather than invent a credit.
18
86
  */
19
87
  import type { Lineage } from "@intentius/chant/audit/catalog";
20
88
 
21
- export const terraformAuditLineage: Record<string, Lineage[]> = {};
89
+ const TFSEC_V061 = "https://github.com/aquasecurity/tfsec/blob/v0.61.3/docs/checks/general/secrets";
90
+ /** tflint's official language ruleset. */
91
+ const TFLINT_RULES = "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules";
92
+ /** The community ruleset with the richest set of language rules outside the official one. */
93
+ const REDEPLOY_RULES = "https://github.com/RedeployAB/tflint-ruleset-redeploy/blob/main/docs/rules";
94
+
95
+ export const terraformAuditLineage: Record<string, Lineage[]> = {
96
+ TF024: [
97
+ {
98
+ tool: "choudoufu",
99
+ rule: "RuleStateBackend",
100
+ url: "https://github.com/INTENTIUS/choudoufu/blob/main/internal/live/lint/issue.go",
101
+ relation: "equivalent",
102
+ },
103
+ ],
104
+ TF002: [
105
+ {
106
+ tool: "tflint-ruleset-terraform",
107
+ rule: "terraform_required_providers",
108
+ url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_required_providers.md",
109
+ relation: "equivalent",
110
+ },
111
+ {
112
+ tool: "terraform-sentinel-policies",
113
+ rule: "require-all-providers-have-version-constraint",
114
+ url: "https://github.com/hashicorp/terraform-sentinel-policies/tree/main/cloud-agnostic",
115
+ relation: "equivalent",
116
+ },
117
+ {
118
+ tool: "aws-terraform-prescriptive-guidance",
119
+ rule: "Add automated version checks",
120
+ url: "https://docs.aws.amazon.com/prescriptive-guidance/latest/terraform-aws-provider-best-practices/version.html#add-automated-version-checks",
121
+ relation: "equivalent",
122
+ },
123
+ ],
124
+ // Sentinel's restrict-terraform-versions checks that required_version is
125
+ // within an allowed range, not that it is present at all: a narrower
126
+ // question than TF003's, so "overlaps" rather than "equivalent" (#2109).
127
+ TF003: [
128
+ {
129
+ tool: "tflint-ruleset-terraform",
130
+ rule: "terraform_required_version",
131
+ url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_required_version.md",
132
+ relation: "equivalent",
133
+ },
134
+ {
135
+ tool: "terraform-sentinel-policies",
136
+ rule: "restrict-terraform-versions",
137
+ url: "https://github.com/hashicorp/terraform-sentinel-policies/tree/main/cloud-agnostic",
138
+ relation: "overlaps",
139
+ },
140
+ {
141
+ tool: "hashicorp-style-guide",
142
+ rule: "Version pinning",
143
+ url: "https://developer.hashicorp.com/terraform/language/style#version-pinning",
144
+ relation: "overlaps",
145
+ },
146
+ ],
147
+ TF004: [
148
+ {
149
+ tool: "tflint-ruleset-terraform",
150
+ rule: "terraform_module_version",
151
+ url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_module_version.md",
152
+ relation: "equivalent",
153
+ },
154
+ {
155
+ tool: "checkov",
156
+ rule: "CKV_TF_2",
157
+ url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/module/generic/RevisionVersionTag.py",
158
+ relation: "equivalent",
159
+ },
160
+ ],
161
+ // checkov's CKV_TF_1 accepts any `?ref=`/`&ref=` matching `[?&](ref=).*(\d\.\d).*`,
162
+ // which a branch literally named `v1.2-dev` passes; TF005 requires a full
163
+ // semver tag or a 40-hex SHA, so "extends" (a strict superset), not
164
+ // "equivalent". KICS's query only matches the `git::` prefix, not the
165
+ // github.com/bitbucket.org shorthands or scp-style sources TF005 also
166
+ // covers, so "overlaps" (#2109).
167
+ TF005: [
168
+ {
169
+ tool: "tflint-ruleset-terraform",
170
+ rule: "terraform_module_pinned_source",
171
+ url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_module_pinned_source.md",
172
+ relation: "equivalent",
173
+ },
174
+ {
175
+ tool: "checkov",
176
+ rule: "CKV_TF_1",
177
+ url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/module/generic/RevisionHash.py",
178
+ relation: "extends",
179
+ },
180
+ {
181
+ tool: "kics",
182
+ rule: "3a81fc06-566f-492a-91dd-7448e409e2cd",
183
+ url: "https://docs.kics.io/latest/queries/terraform-queries/3a81fc06-566f-492a-91dd-7448e409e2cd/",
184
+ relation: "overlaps",
185
+ },
186
+ ],
187
+ TF006: [
188
+ {
189
+ tool: "tflint-ruleset-avm",
190
+ rule: "avm_terraform_sensitive_variable_default_disallowed",
191
+ url: "https://github.com/Azure/tflint-ruleset-avm/blob/main/docs/basic/avm_terraform_sensitive_variable_default_disallowed.md",
192
+ relation: "equivalent",
193
+ },
194
+ ],
195
+ TF007: [
196
+ { tool: "tfsec", rule: "general-secrets-sensitive-in-variable", url: `${TFSEC_V061}/sensitive-in-variable.md`, relation: "equivalent" },
197
+ { tool: "tfsec", rule: "general-secrets-sensitive-in-local", url: `${TFSEC_V061}/sensitive-in-local.md`, relation: "equivalent" },
198
+ ],
199
+ TF008: [
200
+ { tool: "checkov", rule: "CKV_AWS_41", url: "https://www.checkov.io/5.Policy%20Index/terraform.html", relation: "overlaps" },
201
+ { tool: "kics", rule: "d7b9d850-3e06-4a75-852f-c46c2e92240b", url: "https://docs.kics.io/latest/queries/terraform-queries/aws/d7b9d850-3e06-4a75-852f-c46c2e92240b/", relation: "overlaps" },
202
+ {
203
+ tool: "semgrep",
204
+ rule: "terraform.aws.security.aws-provider-static-credentials",
205
+ url: "https://github.com/semgrep/semgrep-rules/blob/develop/terraform/aws/security/aws-provider-static-credentials.yaml",
206
+ relation: "overlaps",
207
+ },
208
+ ],
209
+ TF009: [
210
+ {
211
+ tool: "hashicorp-style-guide",
212
+ rule: "Variables: for sensitive variables, such as passwords and private keys, set the sensitive parameter to true",
213
+ url: "https://developer.hashicorp.com/terraform/language/style#variables",
214
+ relation: "equivalent",
215
+ },
216
+ ],
217
+ TF010: [
218
+ { tool: "tflint-ruleset-terraform", rule: "terraform_typed_variables", url: `${TFLINT_RULES}/terraform_typed_variables.md`, relation: "equivalent" },
219
+ { tool: "kics", rule: "fc5109bf-01fd-49fb-8bde-4492b543c34a", url: "https://docs.kics.io/latest/queries/terraform-queries/fc5109bf-01fd-49fb-8bde-4492b543c34a/", relation: "equivalent" },
220
+ ],
221
+ TF011: [
222
+ { tool: "tflint-ruleset-terraform", rule: "terraform_documented_variables", url: `${TFLINT_RULES}/terraform_documented_variables.md`, relation: "equivalent" },
223
+ { tool: "kics", rule: "2a153952-2544-4687-bcc9-cc8fea814a9b", url: "https://docs.kics.io/latest/queries/terraform-queries/2a153952-2544-4687-bcc9-cc8fea814a9b/", relation: "equivalent" },
224
+ {
225
+ tool: "terraform-sentinel-policies",
226
+ rule: "validate-variables-have-descriptions",
227
+ url: "https://github.com/hashicorp/terraform-sentinel-policies/blob/main/cloud-agnostic/validate-variables-have-descriptions.sentinel",
228
+ relation: "equivalent",
229
+ },
230
+ ],
231
+ TF012: [
232
+ { tool: "tflint-ruleset-terraform", rule: "terraform_documented_outputs", url: `${TFLINT_RULES}/terraform_documented_outputs.md`, relation: "equivalent" },
233
+ { tool: "kics", rule: "59312e8a-a64e-41e7-a252-618533dd1ea8", url: "https://docs.kics.io/latest/queries/terraform-queries/59312e8a-a64e-41e7-a252-618533dd1ea8/", relation: "equivalent" },
234
+ ],
235
+ TF013: [
236
+ { tool: "tflint-ruleset-redeploy", rule: "terraform_ignore_changes_all", url: `${REDEPLOY_RULES}/terraform_ignore_changes_all.md`, relation: "equivalent" },
237
+ ],
238
+ TF014: [
239
+ {
240
+ tool: "tflint-ruleset-avm",
241
+ rule: "avm_terraform_provider_block_disallowed",
242
+ url: "https://github.com/Azure/tflint-ruleset-avm/blob/main/docs/basic/avm_terraform_provider_block_disallowed.md",
243
+ relation: "equivalent",
244
+ },
245
+ {
246
+ tool: "gcp-terraform-best-practices",
247
+ rule: "Build reusable modules: modules must not configure providers",
248
+ url: "https://docs.cloud.google.com/docs/terraform/best-practices/general-style-structure#reusable-modules",
249
+ relation: "equivalent",
250
+ },
251
+ {
252
+ tool: "aws-terraform-prescriptive-guidance",
253
+ rule: "Structure: declare provider configurations in the root module",
254
+ url: "https://docs.aws.amazon.com/prescriptive-guidance/latest/terraform-aws-provider-best-practices/structure.html",
255
+ relation: "equivalent",
256
+ },
257
+ ],
258
+ // TF015 has no scanner to credit: no ruleset in the survey checks for a
259
+ // backend or cloud block inside a child module.
260
+ // tflint-ruleset-terraform's index has nothing about backends at all,
261
+ // Redeploy's has nothing either, and AVM's module rules stop at the
262
+ // provider block TF014 credits. What states the rule is Google Cloud's
263
+ // reusable-modules guide, so that document is the single credit here, and
264
+ // the page section says plainly that the credit is a document rather than
265
+ // a tool (#2112).
266
+ TF015: [
267
+ {
268
+ tool: "gcp-terraform-best-practices",
269
+ rule: "Build reusable modules: state configuration belongs to the root module",
270
+ url: "https://docs.cloud.google.com/docs/terraform/best-practices/general-style-structure#reusable-modules",
271
+ relation: "equivalent",
272
+ },
273
+ ],
274
+ TF016: [
275
+ // `overlaps`, not `equivalent`: tflint reports the same `x = "${y}"` form
276
+ // and also the object-key case (`"${var.k}" = v`), which this rule leaves
277
+ // alone. See tf016.ts.
278
+ { tool: "tflint-ruleset-terraform", rule: "terraform_deprecated_interpolation", url: `${TFLINT_RULES}/terraform_deprecated_interpolation.md`, relation: "overlaps" },
279
+ ],
280
+ TF017: [
281
+ { tool: "tflint-ruleset-redeploy", rule: "terraform_module_depends_on", url: `${REDEPLOY_RULES}/terraform_module_depends_on.md`, relation: "equivalent" },
282
+ ],
283
+ TF018: [
284
+ { tool: "tflint-ruleset-redeploy", rule: "terraform_output_resource", url: `${REDEPLOY_RULES}/terraform_output_resource.md`, relation: "equivalent" },
285
+ ],
286
+ TF019: [
287
+ { tool: "tflint-ruleset-redeploy", rule: "terraform_redundant_default", url: `${REDEPLOY_RULES}/terraform_redundant_default.md`, relation: "equivalent" },
288
+ ],
289
+ TF020: [
290
+ // `overlaps`: tflint evaluates expressions and reports the same four
291
+ // kinds of declaration; chant's index is a string scan over hcl2json's
292
+ // preserved expression strings, so it is generous where tflint is exact
293
+ // (see hcl/references.ts and tf020.ts).
294
+ {
295
+ tool: "tflint-ruleset-terraform",
296
+ rule: "terraform_unused_declarations",
297
+ url: `${TFLINT_RULES}/terraform_unused_declarations.md`,
298
+ relation: "overlaps",
299
+ },
300
+ ],
301
+ TF021: [
302
+ // Redeploy reports every `count` over a collection; TF021 reports only
303
+ // the ones that build an identity out of `count.index`, which is a
304
+ // strict subset, hence `overlaps` on both credits rather than
305
+ // `equivalent`. choudoufu's own rule is the survey's deepest reading of
306
+ // the identity-bearing question (#2112).
307
+ {
308
+ tool: "tflint-ruleset-redeploy",
309
+ rule: "terraform_prefer_for_each",
310
+ url: `${REDEPLOY_RULES}/terraform_prefer_for_each.md`,
311
+ relation: "overlaps",
312
+ },
313
+ {
314
+ tool: "choudoufu",
315
+ rule: "RuleCountIndex",
316
+ url: "https://github.com/INTENTIUS/choudoufu/blob/main/internal/live/lint/count_index.go",
317
+ relation: "overlaps",
318
+ },
319
+ {
320
+ tool: "hashicorp-style-guide",
321
+ rule: "Resources: use for_each for a dynamic resource count",
322
+ url: "https://developer.hashicorp.com/terraform/language/style#dynamic-resource-count",
323
+ relation: "overlaps",
324
+ },
325
+ ],
326
+ TF022: [
327
+ // tfsec consolidated its three `general/secrets` checks into
328
+ // `no-plaintext-exposure` in v1, which is the one still in the repository's
329
+ // `master` docs; `overlaps` because that consolidation also covers the
330
+ // variable and locals cases TF007 reports separately.
331
+ {
332
+ tool: "tfsec",
333
+ rule: "general-secrets-no-plaintext-exposure",
334
+ url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/general/secrets/no-plaintext-exposure/index.md",
335
+ relation: "overlaps",
336
+ },
337
+ { tool: "kics", rule: "a88baa34-e2ad-44ea-ad6f-8cac87bc7c71", url: "https://docs.kics.io/latest/secrets/", relation: "overlaps" },
338
+ ],
339
+ };
@@ -1,9 +1,13 @@
1
1
  import { dirname, join } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
+ import { copyFileSync, mkdtempSync, rmSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
3
5
  import { describe, expect, test } from "vitest";
4
6
  import { auditFiles, type ChecksProvider, type EntitiesProvider } from "@intentius/chant/audit/core";
5
- import { discoverByDetection, type DetectPlugin } from "@intentius/chant/audit/discover";
7
+ import { collectCandidates, discoverByDetection, type DetectPlugin } from "@intentius/chant/audit/discover";
8
+ import { auditTerraformState } from "@intentius/chant/audit/terraform-state";
6
9
  import { terraformPlugin } from "../plugin";
10
+ import { postSynthChecks } from "./post-synth";
7
11
 
8
12
  /**
9
13
  * End-to-end audit test (#2085 acceptance): `chant audit` (`auditFiles` on top
@@ -43,3 +47,38 @@ describe("chant audit against a discovered terraform root module", () => {
43
47
  expect(findings.filter((f) => f.checkId === "TF001")).toHaveLength(0);
44
48
  });
45
49
  });
50
+
51
+ /**
52
+ * TF023 (#2110) is audit-only: it reads the file list `chant audit` discovers,
53
+ * never a parsed root module, so it lives in core
54
+ * (`packages/core/src/audit/terraform-state.ts`) beside the other
55
+ * lexicon-independent families. These run the two halves it depends on, the
56
+ * real walk and the real check, over the committed fixture roots.
57
+ */
58
+ describe("TF023: Terraform state committed to the repository", () => {
59
+ const tf023 = join(dirname(fileURLToPath(import.meta.url)), "post-synth", "fixtures", "TF023");
60
+
61
+ test("fires on a root whose state file is committed beside it", () => {
62
+ // The fixture ships its state as `state.json`: a tracked `terraform.tfstate`
63
+ // is exactly what this rule reports, and the repository's own push rules
64
+ // refuse the name. The test materialises the root the rule is written for.
65
+ const root = mkdtempSync(join(tmpdir(), "chant-tf023-"));
66
+ copyFileSync(join(tf023, "positive", "main.tf"), join(root, "main.tf"));
67
+ copyFileSync(join(tf023, "positive", "state.json"), join(root, "terraform.tfstate"));
68
+ const findings = auditTerraformState(collectCandidates(root));
69
+ rmSync(root, { recursive: true, force: true });
70
+ expect(findings).toHaveLength(1);
71
+ expect(findings[0].checkId).toBe("TF023");
72
+ expect(findings[0].file).toBe("terraform.tfstate");
73
+ expect(findings[0].lexicon).toBe("terraform");
74
+ });
75
+
76
+ test("says nothing about a root that keeps its state remote", () => {
77
+ expect(auditTerraformState(collectCandidates(join(tf023, "negative")))).toEqual([]);
78
+ });
79
+
80
+ test("never runs during a build: it is not one of this lexicon's post-synth checks", () => {
81
+ expect(postSynthChecks.map((c) => c.id)).not.toContain("TF023");
82
+ expect(terraformPlugin.auditCatalog?.()).not.toHaveProperty("TF023");
83
+ });
84
+ });
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Shared reading helpers for the single-block post-synth checks (#2110).
3
+ *
4
+ * Thirteen of the fourteen rules in that issue do the same three things: pick
5
+ * the entities of one block type out of `ctx.entities`, read one attribute of
6
+ * each body, and report. This module is those three things, so a rule file is
7
+ * its condition and its message and nothing else. It exports no
8
+ * `PostSynthCheck`, so the generated barrel skips it (see
9
+ * `packages/core/src/codegen/generate-post-synth-barrel.ts`).
10
+ */
11
+
12
+ import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
13
+ import { isResourceDeclarable } from "@intentius/chant/declarable";
14
+ import type { BlockBody, TerraformEntity } from "../../hcl/parse";
15
+
16
+ /** One parsed block, with the entity key the diagnostic reports against. */
17
+ export interface TerraformBlock {
18
+ /** `ctx.entities` key: `<root>/<address>`. */
19
+ key: string;
20
+ address: string;
21
+ body: BlockBody;
22
+ file: string;
23
+ root: string;
24
+ /** The raw text of the file this block came from (see `TerraformEntity.props`). */
25
+ source: string;
26
+ }
27
+
28
+ /** Every block of one entity type, in `ctx.entities` order. */
29
+ export function blocksOfType(ctx: PostSynthContext, entityType: string): TerraformBlock[] {
30
+ const blocks: TerraformBlock[] = [];
31
+ for (const [key, entity] of ctx.entities) {
32
+ if (entity.entityType !== entityType) continue;
33
+ if (!isResourceDeclarable(entity)) continue;
34
+ const props = (entity as TerraformEntity).props as Partial<TerraformEntity["props"]>;
35
+ blocks.push({
36
+ key,
37
+ address: typeof props.address === "string" ? props.address : key,
38
+ body: (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody,
39
+ file: typeof props.file === "string" ? props.file : "",
40
+ root: typeof props.root === "string" ? props.root : "",
41
+ source: typeof props.source === "string" ? props.source : "",
42
+ });
43
+ }
44
+ return blocks;
45
+ }
46
+
47
+ /** Blocks of several types at once, in the order the types are given. */
48
+ export function blocksOfTypes(ctx: PostSynthContext, entityTypes: string[]): TerraformBlock[] {
49
+ return entityTypes.flatMap((t) => blocksOfType(ctx, t));
50
+ }
51
+
52
+ /** The name half of a `var.<name>` / `output.<name>` address. */
53
+ export function blockName(address: string): string {
54
+ const dot = address.indexOf(".");
55
+ return dot === -1 ? address : address.slice(dot + 1);
56
+ }
57
+
58
+ /**
59
+ * The bodies of a nested block (`lifecycle`, `validation`, a provider's
60
+ * `assume_role`). hcl2json encodes a repeatable block as an array of bodies
61
+ * and a singleton as a bare object, so both are normalized to an array here.
62
+ */
63
+ export function nestedBodies(body: BlockBody, key: string): BlockBody[] {
64
+ const value = body[key];
65
+ if (Array.isArray(value)) return value.filter((v): v is BlockBody => typeof v === "object" && v !== null && !Array.isArray(v));
66
+ if (typeof value === "object" && value !== null) return [value as BlockBody];
67
+ return [];
68
+ }
69
+
70
+ /** Is this attribute value a string that says nothing? */
71
+ export function isBlank(value: unknown): boolean {
72
+ return value === undefined || value === null || (typeof value === "string" && value.trim() === "");
73
+ }
74
+
75
+ /** One scalar attribute found by {@link walkAttributes}. */
76
+ export interface Attribute {
77
+ /** The attribute's own name (the last path segment). */
78
+ name: string;
79
+ /** Dotted path from the block body, e.g. `connection.password`. */
80
+ path: string;
81
+ value: unknown;
82
+ }
83
+
84
+ /**
85
+ * Every scalar attribute in a block body, descending through nested blocks so
86
+ * a credential inside `connection {}` or `assume_role {}` is not invisible.
87
+ * Depth-limited: a Terraform block nests a handful of levels at most, and an
88
+ * unbounded walk over a large body buys nothing.
89
+ */
90
+ export function walkAttributes(body: BlockBody, maxDepth = 3): Attribute[] {
91
+ const out: Attribute[] = [];
92
+ const visit = (value: unknown, path: string, name: string, depth: number): void => {
93
+ if (Array.isArray(value)) {
94
+ if (depth > maxDepth) return;
95
+ for (const item of value) visit(item, path, name, depth);
96
+ return;
97
+ }
98
+ if (typeof value === "object" && value !== null) {
99
+ if (depth >= maxDepth) return;
100
+ for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
101
+ visit(v, path === "" ? k : `${path}.${k}`, k, depth + 1);
102
+ }
103
+ return;
104
+ }
105
+ if (path !== "") out.push({ name, path, value });
106
+ };
107
+ visit(body, "", "", 0);
108
+ return out;
109
+ }
@@ -0,0 +1,28 @@
1
+ terraform {
2
+ required_version = ">= 1.5.0"
3
+
4
+ backend "local" {
5
+ path = "terraform.tfstate"
6
+ }
7
+
8
+ required_providers {
9
+ null = {
10
+ source = "hashicorp/null"
11
+ version = "~> 3.2"
12
+ }
13
+ }
14
+ }
15
+
16
+ provider "null" {}
17
+
18
+ resource "null_resource" "first" {
19
+ triggers = {
20
+ name = "first"
21
+ }
22
+ }
23
+
24
+ resource "null_resource" "second" {
25
+ triggers = {
26
+ name = "second"
27
+ }
28
+ }
@@ -0,0 +1,24 @@
1
+ terraform {
2
+ required_version = ">= 1.5.0"
3
+
4
+ required_providers {
5
+ null = {
6
+ source = "hashicorp/null"
7
+ version = "~> 3.2"
8
+ }
9
+ }
10
+ }
11
+
12
+ provider "null" {}
13
+
14
+ resource "null_resource" "first" {
15
+ triggers = {
16
+ name = "first"
17
+ }
18
+ }
19
+
20
+ resource "null_resource" "second" {
21
+ triggers = {
22
+ name = "second"
23
+ }
24
+ }
@@ -0,0 +1,17 @@
1
+ terraform {
2
+ required_version = ">= 1.5.0"
3
+ required_providers {
4
+ aws = {
5
+ source = "hashicorp/aws"
6
+ version = "~> 5.0"
7
+ }
8
+ }
9
+ }
10
+
11
+ provider "aws" {
12
+ region = "us-east-1"
13
+ }
14
+
15
+ resource "aws_instance" "web" {
16
+ ami = "ami-123"
17
+ }
@@ -0,0 +1,10 @@
1
+ terraform {
2
+ required_version = ">= 1.5.0"
3
+ required_providers {
4
+ aws = "~> 4.0"
5
+ }
6
+ }
7
+
8
+ resource "aws_instance" "web" {
9
+ ami = "ami-123"
10
+ }
@@ -0,0 +1,12 @@
1
+ terraform {
2
+ required_version = ">= 1.5.0"
3
+ required_providers {
4
+ aws = {
5
+ source = "hashicorp/aws"
6
+ }
7
+ }
8
+ }
9
+
10
+ resource "aws_instance" "web" {
11
+ ami = "ami-123"
12
+ }
@@ -0,0 +1,7 @@
1
+ terraform {
2
+ required_version = ">= 1.5.0"
3
+ }
4
+
5
+ resource "google_compute_instance" "vm" {
6
+ name = "vm"
7
+ }
@@ -0,0 +1,10 @@
1
+ terraform {
2
+ required_version = ">= 1.5.0"
3
+
4
+ required_providers {
5
+ aws = {
6
+ source = "hashicorp/aws"
7
+ version = "~> 5.0"
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,8 @@
1
+ terraform {
2
+ required_providers {
3
+ aws = {
4
+ source = "hashicorp/aws"
5
+ version = "~> 5.0"
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,3 @@
1
+ module "git" {
2
+ source = "git::https://example.com/vpc.git?ref=v1.2.0"
3
+ }
@@ -0,0 +1,3 @@
1
+ module "local" {
2
+ source = "./modules/local"
3
+ }
@@ -0,0 +1,4 @@
1
+ module "vpc" {
2
+ source = "terraform-aws-modules/vpc/aws"
3
+ version = "~> 5.0"
4
+ }
@@ -0,0 +1,3 @@
1
+ module "vpc" {
2
+ source = "terraform-aws-modules/vpc/aws"
3
+ }
@@ -0,0 +1,3 @@
1
+ module "local" {
2
+ source = "./modules/local"
3
+ }
@@ -0,0 +1,3 @@
1
+ module "vpc" {
2
+ source = "terraform-aws-modules/vpc/aws"
3
+ }
@@ -0,0 +1,3 @@
1
+ module "vpc" {
2
+ source = "git::https://example.com/vpc.git?ref=abcdef1234567890abcdef1234567890abcdef12"
3
+ }
@@ -0,0 +1,3 @@
1
+ module "vpc" {
2
+ source = "git::https://example.com/vpc.git?ref=v1.2.0"
3
+ }