@intentius/chant-lexicon-terraform 0.57.0 → 0.59.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 (408) hide show
  1. package/README.md +9 -0
  2. package/dist/composites/terraform-adopt-op.d.ts +131 -0
  3. package/dist/composites/terraform-adopt-op.d.ts.map +1 -0
  4. package/dist/composites/terraform-apply-op.d.ts +70 -17
  5. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  6. package/dist/composites/terraform-watch-op.d.ts +104 -37
  7. package/dist/composites/terraform-watch-op.d.ts.map +1 -1
  8. package/dist/config.d.ts +49 -2
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/describe-resources.d.ts +256 -16
  11. package/dist/describe-resources.d.ts.map +1 -1
  12. package/dist/hcl/descend.d.ts +98 -0
  13. package/dist/hcl/descend.d.ts.map +1 -0
  14. package/dist/hcl/marker.d.ts +130 -0
  15. package/dist/hcl/marker.d.ts.map +1 -0
  16. package/dist/hcl/parse.d.ts +156 -9
  17. package/dist/hcl/parse.d.ts.map +1 -1
  18. package/dist/hcl/references.d.ts +76 -0
  19. package/dist/hcl/references.d.ts.map +1 -0
  20. package/dist/hcl/roots.d.ts +22 -3
  21. package/dist/hcl/roots.d.ts.map +1 -1
  22. package/dist/hcl/suppressions.d.ts +54 -0
  23. package/dist/hcl/suppressions.d.ts.map +1 -0
  24. package/dist/hcl/value.d.ts +77 -0
  25. package/dist/hcl/value.d.ts.map +1 -0
  26. package/dist/index.d.ts +5 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/integrity.json +34 -5
  29. package/dist/lint/audit-catalog.d.ts +3 -2
  30. package/dist/lint/audit-catalog.d.ts.map +1 -1
  31. package/dist/lint/audit-lineage.d.ts +95 -0
  32. package/dist/lint/audit-lineage.d.ts.map +1 -1
  33. package/dist/lint/post-synth/blocks.d.ts +53 -0
  34. package/dist/lint/post-synth/blocks.d.ts.map +1 -0
  35. package/dist/lint/post-synth/fixtures/load.d.ts +37 -0
  36. package/dist/lint/post-synth/fixtures/load.d.ts.map +1 -0
  37. package/dist/lint/post-synth/index.d.ts.map +1 -1
  38. package/dist/lint/post-synth/module-source.d.ts +38 -0
  39. package/dist/lint/post-synth/module-source.d.ts.map +1 -0
  40. package/dist/lint/post-synth/scope.d.ts +65 -0
  41. package/dist/lint/post-synth/scope.d.ts.map +1 -0
  42. package/dist/lint/post-synth/tf001.d.ts +58 -6
  43. package/dist/lint/post-synth/tf001.d.ts.map +1 -1
  44. package/dist/lint/post-synth/tf002.d.ts +32 -0
  45. package/dist/lint/post-synth/tf002.d.ts.map +1 -0
  46. package/dist/lint/post-synth/tf003.d.ts +22 -0
  47. package/dist/lint/post-synth/tf003.d.ts.map +1 -0
  48. package/dist/lint/post-synth/tf004.d.ts +22 -0
  49. package/dist/lint/post-synth/tf004.d.ts.map +1 -0
  50. package/dist/lint/post-synth/tf005.d.ts +28 -0
  51. package/dist/lint/post-synth/tf005.d.ts.map +1 -0
  52. package/dist/lint/post-synth/tf006.d.ts +21 -0
  53. package/dist/lint/post-synth/tf006.d.ts.map +1 -0
  54. package/dist/lint/post-synth/tf007.d.ts +21 -0
  55. package/dist/lint/post-synth/tf007.d.ts.map +1 -0
  56. package/dist/lint/post-synth/tf008.d.ts +22 -0
  57. package/dist/lint/post-synth/tf008.d.ts.map +1 -0
  58. package/dist/lint/post-synth/tf009.d.ts +24 -0
  59. package/dist/lint/post-synth/tf009.d.ts.map +1 -0
  60. package/dist/lint/post-synth/tf010.d.ts +19 -0
  61. package/dist/lint/post-synth/tf010.d.ts.map +1 -0
  62. package/dist/lint/post-synth/tf011.d.ts +18 -0
  63. package/dist/lint/post-synth/tf011.d.ts.map +1 -0
  64. package/dist/lint/post-synth/tf012.d.ts +17 -0
  65. package/dist/lint/post-synth/tf012.d.ts.map +1 -0
  66. package/dist/lint/post-synth/tf013.d.ts +21 -0
  67. package/dist/lint/post-synth/tf013.d.ts.map +1 -0
  68. package/dist/lint/post-synth/tf014.d.ts +25 -0
  69. package/dist/lint/post-synth/tf014.d.ts.map +1 -0
  70. package/dist/lint/post-synth/tf015.d.ts +30 -0
  71. package/dist/lint/post-synth/tf015.d.ts.map +1 -0
  72. package/dist/lint/post-synth/tf016.d.ts +30 -0
  73. package/dist/lint/post-synth/tf016.d.ts.map +1 -0
  74. package/dist/lint/post-synth/tf017.d.ts +19 -0
  75. package/dist/lint/post-synth/tf017.d.ts.map +1 -0
  76. package/dist/lint/post-synth/tf018.d.ts +20 -0
  77. package/dist/lint/post-synth/tf018.d.ts.map +1 -0
  78. package/dist/lint/post-synth/tf019.d.ts +25 -0
  79. package/dist/lint/post-synth/tf019.d.ts.map +1 -0
  80. package/dist/lint/post-synth/tf020.d.ts +38 -0
  81. package/dist/lint/post-synth/tf020.d.ts.map +1 -0
  82. package/dist/lint/post-synth/tf021.d.ts +41 -0
  83. package/dist/lint/post-synth/tf021.d.ts.map +1 -0
  84. package/dist/lint/post-synth/tf022.d.ts +24 -0
  85. package/dist/lint/post-synth/tf022.d.ts.map +1 -0
  86. package/dist/lint/post-synth/tf024.d.ts +24 -0
  87. package/dist/lint/post-synth/tf024.d.ts.map +1 -0
  88. package/dist/lint/post-synth/tf025.d.ts +29 -0
  89. package/dist/lint/post-synth/tf025.d.ts.map +1 -0
  90. package/dist/lint/post-synth/tf026.d.ts +28 -0
  91. package/dist/lint/post-synth/tf026.d.ts.map +1 -0
  92. package/dist/lint/post-synth/tf027.d.ts +30 -0
  93. package/dist/lint/post-synth/tf027.d.ts.map +1 -0
  94. package/dist/lint/post-synth/tf028.d.ts +33 -0
  95. package/dist/lint/post-synth/tf028.d.ts.map +1 -0
  96. package/dist/lint/rules/fixtures/load.d.ts +13 -0
  97. package/dist/lint/rules/fixtures/load.d.ts.map +1 -0
  98. package/dist/lint/rules/plan-before-apply.d.ts +11 -0
  99. package/dist/lint/rules/plan-before-apply.d.ts.map +1 -1
  100. package/dist/lint/secret-shape.d.ts +68 -0
  101. package/dist/lint/secret-shape.d.ts.map +1 -0
  102. package/dist/live-ownership.d.ts +55 -0
  103. package/dist/live-ownership.d.ts.map +1 -0
  104. package/dist/lsp/option-keys.d.ts.map +1 -1
  105. package/dist/manifest.json +1 -1
  106. package/dist/okf/index.md +26 -0
  107. package/dist/okf/rules/TF002.md +11 -0
  108. package/dist/okf/rules/TF003.md +11 -0
  109. package/dist/okf/rules/TF004.md +11 -0
  110. package/dist/okf/rules/TF005.md +11 -0
  111. package/dist/okf/rules/TF006.md +11 -0
  112. package/dist/okf/rules/TF007.md +11 -0
  113. package/dist/okf/rules/TF008.md +11 -0
  114. package/dist/okf/rules/TF009.md +11 -0
  115. package/dist/okf/rules/TF010.md +11 -0
  116. package/dist/okf/rules/TF011.md +11 -0
  117. package/dist/okf/rules/TF012.md +11 -0
  118. package/dist/okf/rules/TF013.md +11 -0
  119. package/dist/okf/rules/TF014.md +11 -0
  120. package/dist/okf/rules/TF015.md +11 -0
  121. package/dist/okf/rules/TF016.md +11 -0
  122. package/dist/okf/rules/TF017.md +11 -0
  123. package/dist/okf/rules/TF018.md +11 -0
  124. package/dist/okf/rules/TF019.md +11 -0
  125. package/dist/okf/rules/TF020.md +11 -0
  126. package/dist/okf/rules/TF021.md +11 -0
  127. package/dist/okf/rules/TF022.md +11 -0
  128. package/dist/okf/rules/TF024.md +11 -0
  129. package/dist/okf/rules/TF025.md +11 -0
  130. package/dist/okf/rules/TF026.md +11 -0
  131. package/dist/okf/rules/TF027.md +11 -0
  132. package/dist/okf/rules/TF028.md +11 -0
  133. package/dist/op/activities/index.d.ts +13 -6
  134. package/dist/op/activities/index.d.ts.map +1 -1
  135. package/dist/op/activities/live-detect.d.ts +51 -0
  136. package/dist/op/activities/live-detect.d.ts.map +1 -0
  137. package/dist/op/activities/terraform.d.ts +466 -17
  138. package/dist/op/activities/terraform.d.ts.map +1 -1
  139. package/dist/op/activity-contracts.d.ts +148 -0
  140. package/dist/op/activity-contracts.d.ts.map +1 -0
  141. package/dist/op/adoption.d.ts +118 -0
  142. package/dist/op/adoption.d.ts.map +1 -0
  143. package/dist/op/builders.d.ts +48 -6
  144. package/dist/op/builders.d.ts.map +1 -1
  145. package/dist/op/resolve-root-mode.d.ts +59 -0
  146. package/dist/op/resolve-root-mode.d.ts.map +1 -0
  147. package/dist/plugin.d.ts.map +1 -1
  148. package/dist/rules/blocks.ts +109 -0
  149. package/dist/rules/module-source.ts +121 -0
  150. package/dist/rules/plan-before-apply.ts +11 -0
  151. package/dist/rules/scope.ts +107 -0
  152. package/dist/rules/tf001.ts +98 -18
  153. package/dist/rules/tf002.ts +151 -0
  154. package/dist/rules/tf003.ts +71 -0
  155. package/dist/rules/tf004.ts +63 -0
  156. package/dist/rules/tf005.ts +76 -0
  157. package/dist/rules/tf006.ts +53 -0
  158. package/dist/rules/tf007.ts +78 -0
  159. package/dist/rules/tf008.ts +75 -0
  160. package/dist/rules/tf009.ts +59 -0
  161. package/dist/rules/tf010.ts +51 -0
  162. package/dist/rules/tf011.ts +49 -0
  163. package/dist/rules/tf012.ts +47 -0
  164. package/dist/rules/tf013.ts +63 -0
  165. package/dist/rules/tf014.ts +72 -0
  166. package/dist/rules/tf015.ts +72 -0
  167. package/dist/rules/tf016.ts +75 -0
  168. package/dist/rules/tf017.ts +50 -0
  169. package/dist/rules/tf018.ts +72 -0
  170. package/dist/rules/tf019.ts +72 -0
  171. package/dist/rules/tf020.ts +123 -0
  172. package/dist/rules/tf021.ts +126 -0
  173. package/dist/rules/tf022.ts +61 -0
  174. package/dist/rules/tf024.ts +70 -0
  175. package/dist/rules/tf025.ts +101 -0
  176. package/dist/rules/tf026.ts +84 -0
  177. package/dist/rules/tf027.ts +74 -0
  178. package/dist/rules/tf028.ts +96 -0
  179. package/dist/skills/chant-terraform.md +1 -1
  180. package/dist/validate.d.ts.map +1 -1
  181. package/package.json +7 -2
  182. package/src/__fixtures__/ACCEPTANCE.md +181 -0
  183. package/src/__fixtures__/live/main.tf +28 -0
  184. package/src/__fixtures__/live-adopt/estate.chdf.hcl +6 -0
  185. package/src/__fixtures__/live-adopt/main.tf +21 -0
  186. package/src/__fixtures__/live-estate/README.md +58 -0
  187. package/src/__fixtures__/live-estate/compute.tf +10 -0
  188. package/src/__fixtures__/live-estate/estate.chdf.hcl +1 -0
  189. package/src/__fixtures__/live-estate/logs.tf +8 -0
  190. package/src/__fixtures__/live-estate/network.tf +24 -0
  191. package/src/__fixtures__/live-estate/rule.tf +11 -0
  192. package/src/__fixtures__/live-estate/unowned.tf +21 -0
  193. package/src/__fixtures__/live-estate/versions.tf +31 -0
  194. package/src/__fixtures__/live-ls.json +98 -0
  195. package/src/__fixtures__/live-plan.json +285 -0
  196. package/src/__fixtures__/live-sidecar/estate.chdf.hcl +3 -0
  197. package/src/__fixtures__/live-sidecar/main.tf +24 -0
  198. package/src/__fixtures__/module-tree/README.md +16 -0
  199. package/src/__fixtures__/module-tree/cycle/main.tf +3 -0
  200. package/src/__fixtures__/module-tree/cycle/modules/a/main.tf +9 -0
  201. package/src/__fixtures__/module-tree/cycle/modules/b/main.tf +9 -0
  202. package/src/__fixtures__/module-tree/missing/main.tf +9 -0
  203. package/src/__fixtures__/module-tree/outside/root/main.tf +9 -0
  204. package/src/__fixtures__/module-tree/root/main.tf +28 -0
  205. package/src/__fixtures__/module-tree/root/modules/cdn/main.tf +15 -0
  206. package/src/__fixtures__/module-tree/root/modules/cdn/modules/bucket/main.tf +11 -0
  207. package/src/__fixtures__/module-tree/shared/main.tf +5 -0
  208. package/src/__fixtures__/references/main.tf +56 -0
  209. package/src/__fixtures__/remote-backend/main.tf +34 -0
  210. package/src/__fixtures__/with-backend/main.tf +7 -0
  211. package/src/codegen/lint-rules-page.test.ts +45 -0
  212. package/src/composites/terraform-adopt-op.acceptance.test.ts +193 -0
  213. package/src/composites/terraform-adopt-op.test.ts +175 -0
  214. package/src/composites/terraform-adopt-op.ts +202 -0
  215. package/src/composites/terraform-apply-op.acceptance.test.ts +188 -5
  216. package/src/composites/terraform-apply-op.test.ts +179 -19
  217. package/src/composites/terraform-apply-op.ts +123 -30
  218. package/src/composites/terraform-watch-op.test.ts +169 -29
  219. package/src/composites/terraform-watch-op.ts +183 -75
  220. package/src/config.ts +32 -3
  221. package/src/describe-resources.live.test.ts +598 -0
  222. package/src/describe-resources.test.ts +53 -0
  223. package/src/describe-resources.ts +811 -28
  224. package/src/hcl/descend.test.ts +162 -0
  225. package/src/hcl/descend.ts +273 -0
  226. package/src/hcl/marker.test.ts +147 -0
  227. package/src/hcl/marker.ts +235 -0
  228. package/src/hcl/parse.test.ts +69 -0
  229. package/src/hcl/parse.ts +269 -27
  230. package/src/hcl/references.test.ts +116 -0
  231. package/src/hcl/references.ts +172 -0
  232. package/src/hcl/roots.ts +59 -5
  233. package/src/hcl/suppressions.test.ts +147 -0
  234. package/src/hcl/suppressions.ts +204 -0
  235. package/src/hcl/value.test.ts +73 -0
  236. package/src/hcl/value.ts +106 -0
  237. package/src/index.ts +31 -1
  238. package/src/lint/audit-catalog.ts +288 -3
  239. package/src/lint/audit-lineage.ts +384 -1
  240. package/src/lint/audit.test.ts +210 -3
  241. package/src/lint/post-synth/blocks.ts +109 -0
  242. package/src/lint/post-synth/fixtures/TF001/negative-cloud.tf +28 -0
  243. package/src/lint/post-synth/fixtures/TF001/negative.tf +30 -0
  244. package/src/lint/post-synth/fixtures/TF001/positive-local.tf +31 -0
  245. package/src/lint/post-synth/fixtures/TF001/positive.tf +24 -0
  246. package/src/lint/post-synth/fixtures/TF002/negative.tf +17 -0
  247. package/src/lint/post-synth/fixtures/TF002/positive-legacy.tf +10 -0
  248. package/src/lint/post-synth/fixtures/TF002/positive-no-version.tf +12 -0
  249. package/src/lint/post-synth/fixtures/TF002/positive.tf +7 -0
  250. package/src/lint/post-synth/fixtures/TF003/negative.tf +10 -0
  251. package/src/lint/post-synth/fixtures/TF003/positive.tf +8 -0
  252. package/src/lint/post-synth/fixtures/TF004/negative-git.tf +3 -0
  253. package/src/lint/post-synth/fixtures/TF004/negative-local.tf +3 -0
  254. package/src/lint/post-synth/fixtures/TF004/negative.tf +4 -0
  255. package/src/lint/post-synth/fixtures/TF004/positive.tf +3 -0
  256. package/src/lint/post-synth/fixtures/TF005/negative-local.tf +3 -0
  257. package/src/lint/post-synth/fixtures/TF005/negative-registry.tf +3 -0
  258. package/src/lint/post-synth/fixtures/TF005/negative-sha.tf +3 -0
  259. package/src/lint/post-synth/fixtures/TF005/negative.tf +3 -0
  260. package/src/lint/post-synth/fixtures/TF005/positive-branch.tf +3 -0
  261. package/src/lint/post-synth/fixtures/TF005/positive-non-semver.tf +3 -0
  262. package/src/lint/post-synth/fixtures/TF005/positive.tf +3 -0
  263. package/src/lint/post-synth/fixtures/TF006/negative.tf +5 -0
  264. package/src/lint/post-synth/fixtures/TF006/positive-empty-default.tf +11 -0
  265. package/src/lint/post-synth/fixtures/TF006/positive.tf +6 -0
  266. package/src/lint/post-synth/fixtures/TF007/negative.tf +10 -0
  267. package/src/lint/post-synth/fixtures/TF007/positive.tf +9 -0
  268. package/src/lint/post-synth/fixtures/TF008/negative-variable.tf +10 -0
  269. package/src/lint/post-synth/fixtures/TF008/negative.tf +8 -0
  270. package/src/lint/post-synth/fixtures/TF008/positive-other-provider.tf +10 -0
  271. package/src/lint/post-synth/fixtures/TF008/positive.tf +5 -0
  272. package/src/lint/post-synth/fixtures/TF009/negative.tf +10 -0
  273. package/src/lint/post-synth/fixtures/TF009/positive.tf +4 -0
  274. package/src/lint/post-synth/fixtures/TF010/negative.tf +5 -0
  275. package/src/lint/post-synth/fixtures/TF010/positive.tf +4 -0
  276. package/src/lint/post-synth/fixtures/TF011/negative.tf +5 -0
  277. package/src/lint/post-synth/fixtures/TF011/positive-blank.tf +5 -0
  278. package/src/lint/post-synth/fixtures/TF011/positive.tf +4 -0
  279. package/src/lint/post-synth/fixtures/TF012/negative.tf +4 -0
  280. package/src/lint/post-synth/fixtures/TF012/positive.tf +3 -0
  281. package/src/lint/post-synth/fixtures/TF013/negative.tf +8 -0
  282. package/src/lint/post-synth/fixtures/TF013/positive-data.tf +8 -0
  283. package/src/lint/post-synth/fixtures/TF013/positive.tf +8 -0
  284. package/src/lint/post-synth/fixtures/TF014/negative/main.tf +26 -0
  285. package/src/lint/post-synth/fixtures/TF014/negative/modules/cdn/main.tf +19 -0
  286. package/src/lint/post-synth/fixtures/TF014/positive/main.tf +16 -0
  287. package/src/lint/post-synth/fixtures/TF014/positive/modules/cdn/main.tf +15 -0
  288. package/src/lint/post-synth/fixtures/TF015/negative/main.tf +13 -0
  289. package/src/lint/post-synth/fixtures/TF015/negative/modules/cdn/main.tf +16 -0
  290. package/src/lint/post-synth/fixtures/TF015/positive/main.tf +13 -0
  291. package/src/lint/post-synth/fixtures/TF015/positive/modules/cdn/main.tf +14 -0
  292. package/src/lint/post-synth/fixtures/TF016/negative-template.tf +7 -0
  293. package/src/lint/post-synth/fixtures/TF016/negative.tf +16 -0
  294. package/src/lint/post-synth/fixtures/TF016/positive-shared-file.tf +12 -0
  295. package/src/lint/post-synth/fixtures/TF016/positive.tf +13 -0
  296. package/src/lint/post-synth/fixtures/TF017/negative.tf +4 -0
  297. package/src/lint/post-synth/fixtures/TF017/positive.tf +5 -0
  298. package/src/lint/post-synth/fixtures/TF018/negative.tf +9 -0
  299. package/src/lint/post-synth/fixtures/TF018/positive-data.tf +9 -0
  300. package/src/lint/post-synth/fixtures/TF018/positive.tf +4 -0
  301. package/src/lint/post-synth/fixtures/TF019/negative.tf +14 -0
  302. package/src/lint/post-synth/fixtures/TF019/positive-output.tf +5 -0
  303. package/src/lint/post-synth/fixtures/TF019/positive.tf +16 -0
  304. package/src/lint/post-synth/fixtures/TF020/negative.tf +43 -0
  305. package/src/lint/post-synth/fixtures/TF020/positive.tf +32 -0
  306. package/src/lint/post-synth/fixtures/TF020/scoped/main.tf +21 -0
  307. package/src/lint/post-synth/fixtures/TF020/scoped/modules/cdn/main.tf +13 -0
  308. package/src/lint/post-synth/fixtures/TF021/negative.tf +31 -0
  309. package/src/lint/post-synth/fixtures/TF021/positive-length.tf +8 -0
  310. package/src/lint/post-synth/fixtures/TF021/positive.tf +8 -0
  311. package/src/lint/post-synth/fixtures/TF022/negative-locator.tf +7 -0
  312. package/src/lint/post-synth/fixtures/TF022/negative.tf +10 -0
  313. package/src/lint/post-synth/fixtures/TF022/positive-nested.tf +10 -0
  314. package/src/lint/post-synth/fixtures/TF022/positive.tf +6 -0
  315. package/src/lint/post-synth/fixtures/TF023/negative/main.tf +15 -0
  316. package/src/lint/post-synth/fixtures/TF023/positive/main.tf +9 -0
  317. package/src/lint/post-synth/fixtures/TF023/positive/state.json +24 -0
  318. package/src/lint/post-synth/fixtures/TF024/live-clean/main.tf +9 -0
  319. package/src/lint/post-synth/fixtures/TF024/live-with-backend/main.tf +14 -0
  320. package/src/lint/post-synth/fixtures/TF024/live-with-cloud/main.tf +16 -0
  321. package/src/lint/post-synth/fixtures/TF025/live-clean/main.tf +13 -0
  322. package/src/lint/post-synth/fixtures/TF025/live-with-workspace-ref/main.tf +13 -0
  323. package/src/lint/post-synth/fixtures/TF026/live-default/main.tf +9 -0
  324. package/src/lint/post-synth/fixtures/TF026/live-explicit-delete/main.tf +13 -0
  325. package/src/lint/post-synth/fixtures/TF026/live-keep/main.tf +13 -0
  326. package/src/lint/post-synth/fixtures/TF027/live-default/main.tf +9 -0
  327. package/src/lint/post-synth/fixtures/TF027/live-untagged-delete/main.tf +13 -0
  328. package/src/lint/post-synth/fixtures/TF027/live-untagged-delete-scoped/main.tf +17 -0
  329. package/src/lint/post-synth/fixtures/TF027/live-untagged-report/main.tf +13 -0
  330. package/src/lint/post-synth/fixtures/TF028/live/main.tf +9 -0
  331. package/src/lint/post-synth/fixtures/TF028/stock/main.tf +10 -0
  332. package/src/lint/post-synth/fixtures/load.ts +108 -0
  333. package/src/lint/post-synth/index.ts +52 -0
  334. package/src/lint/post-synth/live-refusals-config-format.test.ts +143 -0
  335. package/src/lint/post-synth/module-rules.test.ts +153 -0
  336. package/src/lint/post-synth/module-source.test.ts +75 -0
  337. package/src/lint/post-synth/module-source.ts +121 -0
  338. package/src/lint/post-synth/post-synth.test.ts +73 -16
  339. package/src/lint/post-synth/scope.ts +107 -0
  340. package/src/lint/post-synth/single-block.test.ts +263 -0
  341. package/src/lint/post-synth/suppression.test.ts +110 -0
  342. package/src/lint/post-synth/tf001.ts +98 -18
  343. package/src/lint/post-synth/tf002.test.ts +93 -0
  344. package/src/lint/post-synth/tf002.ts +151 -0
  345. package/src/lint/post-synth/tf003.test.ts +75 -0
  346. package/src/lint/post-synth/tf003.ts +71 -0
  347. package/src/lint/post-synth/tf004.test.ts +47 -0
  348. package/src/lint/post-synth/tf004.ts +63 -0
  349. package/src/lint/post-synth/tf005.test.ts +57 -0
  350. package/src/lint/post-synth/tf005.ts +76 -0
  351. package/src/lint/post-synth/tf006.ts +53 -0
  352. package/src/lint/post-synth/tf007.ts +78 -0
  353. package/src/lint/post-synth/tf008.ts +75 -0
  354. package/src/lint/post-synth/tf009.ts +59 -0
  355. package/src/lint/post-synth/tf010.ts +51 -0
  356. package/src/lint/post-synth/tf011.ts +49 -0
  357. package/src/lint/post-synth/tf012.ts +47 -0
  358. package/src/lint/post-synth/tf013.ts +63 -0
  359. package/src/lint/post-synth/tf014.ts +72 -0
  360. package/src/lint/post-synth/tf015.ts +72 -0
  361. package/src/lint/post-synth/tf016.ts +75 -0
  362. package/src/lint/post-synth/tf017.ts +50 -0
  363. package/src/lint/post-synth/tf018.ts +72 -0
  364. package/src/lint/post-synth/tf019.ts +72 -0
  365. package/src/lint/post-synth/tf020.ts +123 -0
  366. package/src/lint/post-synth/tf021.ts +126 -0
  367. package/src/lint/post-synth/tf022.ts +61 -0
  368. package/src/lint/post-synth/tf024.test.ts +42 -0
  369. package/src/lint/post-synth/tf024.ts +70 -0
  370. package/src/lint/post-synth/tf025.test.ts +46 -0
  371. package/src/lint/post-synth/tf025.ts +101 -0
  372. package/src/lint/post-synth/tf026.test.ts +56 -0
  373. package/src/lint/post-synth/tf026.ts +84 -0
  374. package/src/lint/post-synth/tf027.test.ts +62 -0
  375. package/src/lint/post-synth/tf027.ts +74 -0
  376. package/src/lint/post-synth/tf028.test.ts +92 -0
  377. package/src/lint/post-synth/tf028.ts +96 -0
  378. package/src/lint/presets.test.ts +72 -0
  379. package/src/lint/rules/fixtures/TF101/negative-op.ts +2 -0
  380. package/src/lint/rules/fixtures/TF101/positive-op.ts +2 -0
  381. package/src/lint/rules/fixtures/load.ts +53 -0
  382. package/src/lint/rules/plan-before-apply.test.ts +112 -7
  383. package/src/lint/rules/plan-before-apply.ts +11 -0
  384. package/src/lint/secret-shape.test.ts +150 -0
  385. package/src/lint/secret-shape.ts +159 -0
  386. package/src/live-ownership.ts +60 -0
  387. package/src/lsp/completions.test.ts +3 -2
  388. package/src/lsp/option-keys.ts +12 -6
  389. package/src/op/activities/choudoufu.acceptance.test.ts +127 -0
  390. package/src/op/activities/choudoufu.test.ts +725 -0
  391. package/src/op/activities/index.ts +40 -4
  392. package/src/op/activities/live-detect.test.ts +143 -0
  393. package/src/op/activities/live-detect.ts +162 -0
  394. package/src/op/activities/registry.test.ts +11 -3
  395. package/src/op/activities/terraform.test.ts +7 -3
  396. package/src/op/activities/terraform.ts +861 -69
  397. package/src/op/activity-contracts.test.ts +147 -0
  398. package/src/op/activity-contracts.ts +178 -0
  399. package/src/op/adoption.test.ts +170 -0
  400. package/src/op/adoption.ts +241 -0
  401. package/src/op/builders.test.ts +60 -5
  402. package/src/op/builders.ts +95 -5
  403. package/src/op/resolve-root-mode.test.ts +85 -0
  404. package/src/op/resolve-root-mode.ts +86 -0
  405. package/src/plugin.test.ts +106 -0
  406. package/src/plugin.ts +186 -29
  407. package/src/skills/chant-terraform.md +1 -1
  408. package/src/validate.ts +10 -1
@@ -9,6 +9,7 @@ import { terraformPlugin } from "./plugin";
9
9
  import { terraformConfigSchema } from "./config";
10
10
  import {
11
11
  DATA_TYPE,
12
+ LIVE_TYPE,
12
13
  PROVIDER_TYPE,
13
14
  RESOURCE_TYPE,
14
15
  TERRAFORM_TYPE,
@@ -57,6 +58,12 @@ describe("terraform plugin", () => {
57
58
  expect(terraformPlugin.postSynthChecks?.().length).toBeGreaterThan(0);
58
59
  });
59
60
 
61
+ it("registers the estate-wide reads a live root makes possible (#2104)", () => {
62
+ expect(terraformPlugin.ambientKinds?.()).toEqual(["Terraform::Resource"]);
63
+ expect(typeof terraformPlugin.observeAmbient).toBe("function");
64
+ expect(typeof terraformPlugin.teardownOwned).toBe("function");
65
+ });
66
+
60
67
  it("registers the LSP providers and docs", () => {
61
68
  expect(typeof terraformPlugin.completionProvider).toBe("function");
62
69
  expect(typeof terraformPlugin.hoverProvider).toBe("function");
@@ -227,6 +234,67 @@ describe("buildRoots", () => {
227
234
  });
228
235
  });
229
236
 
237
+ describe("buildRoots: choudoufu live mode (#2103)", () => {
238
+ it("marks every entity live and carries the estate, under binary: choudoufu", async () => {
239
+ const { entities, warnings } = await buildRoots(fixtures, {
240
+ terraform: { binary: "choudoufu", roots: { estate: { dir: "./live" } } },
241
+ });
242
+
243
+ expect(warnings ?? []).toEqual([]);
244
+ expect([...entities.keys()].sort()).toEqual([
245
+ "estate/live",
246
+ "estate/null_resource.first",
247
+ "estate/null_resource.second",
248
+ "estate/provider.null",
249
+ "estate/terraform",
250
+ ]);
251
+
252
+ const live = entities.get("estate/live")!;
253
+ expect(live.entityType).toBe(LIVE_TYPE);
254
+ for (const key of entities.keys()) {
255
+ const props = propsOf(entities.get(key));
256
+ expect(props.mode).toBe("live");
257
+ expect(props.estate).toBe("fixture-estate");
258
+ }
259
+ });
260
+
261
+ it("reads the estate from the estate.chdf.hcl sidecar the same way", async () => {
262
+ const { entities, warnings } = await buildRoots(fixtures, {
263
+ terraform: { binary: "choudoufu", roots: { estate: { dir: "./live-sidecar" } } },
264
+ });
265
+
266
+ expect(warnings ?? []).toEqual([]);
267
+ const live = entities.get("estate/live")!;
268
+ expect(live.entityType).toBe(LIVE_TYPE);
269
+ expect(propsOf(live).file).toBe("estate.chdf.hcl");
270
+ expect(propsOf(entities.get("estate/terraform")).estate).toBe("fixture-estate");
271
+ });
272
+
273
+ it("stays state mode and warns when the estate is declared but the binary is not choudoufu", async () => {
274
+ const { entities, warnings } = await buildRoots(fixtures, {
275
+ terraform: { roots: { estate: { dir: "./live" } } },
276
+ });
277
+
278
+ for (const key of entities.keys()) {
279
+ const props = propsOf(entities.get(key));
280
+ expect(props.mode).toBe("state");
281
+ expect(props.estate).toBeUndefined();
282
+ }
283
+ expect(warnings).toHaveLength(1);
284
+ expect(warnings![0]).toContain("terraform.roots.estate");
285
+ expect(warnings![0]).toContain("inert");
286
+ });
287
+
288
+ it("stays state mode with no live declaration at all, regardless of binary", async () => {
289
+ const { entities, warnings } = await buildRoots(fixtures, {
290
+ terraform: { binary: "choudoufu", roots: { app: { dir: "./with-backend" } } },
291
+ });
292
+
293
+ expect(warnings ?? []).toEqual([]);
294
+ expect(propsOf(entities.get("app/terraform")).mode).toBe("state");
295
+ });
296
+ });
297
+
230
298
  describe("auditEntities (#2085, parse-to-graph for chant audit)", () => {
231
299
  it("parses a joined `# file:` bundle into entities", async () => {
232
300
  const hook = terraformPlugin.auditEntities;
@@ -269,3 +337,41 @@ describe("parseTerraformRootContent", () => {
269
337
  expect([...entities.keys()]).toEqual(["app/locals", "app/locals~2"]);
270
338
  });
271
339
  });
340
+
341
+ describe("detectTemplate", () => {
342
+ const detect = (data: unknown) => terraformPlugin.detectTemplate?.(data) ?? false;
343
+
344
+ it("recognises .tf text by its top-level block headers", () => {
345
+ expect(detect('terraform {\n required_version = ">= 1.5.0"\n}')).toBe(true);
346
+ expect(detect('resource "null_resource" "first" {}')).toBe(true);
347
+ expect(detect('data "aws_ami" "ubuntu" {\n most_recent = true\n}')).toBe(true);
348
+ expect(detect('provider "aws" {\n region = "us-east-1"\n}')).toBe(true);
349
+ expect(detect('module "vpc" {\n source = "./modules/vpc"\n}')).toBe(true);
350
+ expect(detect('variable "region" {\n type = string\n}')).toBe(true);
351
+ expect(detect('output "id" {\n value = null_resource.first.id\n}')).toBe(true);
352
+ });
353
+
354
+ it("recognises the .tf.json syntax by its whole key set", () => {
355
+ expect(detect({ resource: { null_resource: { first: {} } } })).toBe(true);
356
+ expect(detect({ terraform: { required_version: ">= 1.5.0" }, provider: { aws: {} } })).toBe(true);
357
+ });
358
+
359
+ it("refuses another lexicon's document, and anything with no block at all", () => {
360
+ // k8s / fountain / gcp, which share the apiVersion+kind shape.
361
+ expect(detect({ apiVersion: "v1", kind: "ConfigMap", data: {} })).toBe(false);
362
+ // CloudFormation, whose `Resources` is not one of Terraform's block names.
363
+ expect(detect({ AWSTemplateFormatVersion: "2010-09-09", Resources: {} })).toBe(false);
364
+ // Compose.
365
+ expect(detect({ services: { web: { image: "nginx" } } })).toBe(false);
366
+ expect(detect("# just a comment\n")).toBe(false);
367
+ expect(detect("")).toBe(false);
368
+ expect(detect({})).toBe(false);
369
+ expect(detect(null)).toBe(false);
370
+ expect(detect([{ resource: {} }])).toBe(false);
371
+ expect(detect(42)).toBe(false);
372
+ });
373
+
374
+ it("does not match a block header that is only mentioned mid-line", () => {
375
+ expect(detect('# see resource "null_resource" "first" {} in main.tf')).toBe(false);
376
+ });
377
+ });
package/src/plugin.ts CHANGED
@@ -1,16 +1,18 @@
1
- import type { LexiconPlugin } from "@intentius/chant/lexicon";
1
+ import type { AuditEntitiesInput, LexiconPlugin } from "@intentius/chant/lexicon";
2
2
  import type { CompletionContext, HoverContext } from "@intentius/chant/lsp/types";
3
3
  import type { Declarable } from "@intentius/chant/declarable";
4
4
  import { createSkillsLoader } from "@intentius/chant/lexicon-plugin-helpers";
5
5
  import { terraformSerializer } from "./serializer";
6
6
  import { rules } from "./lint/rules";
7
7
  import { postSynthChecks } from "./lint/post-synth";
8
+ import { withCallersChain } from "./lint/post-synth/scope";
8
9
  import { terraformAuditCatalog } from "./lint/audit-catalog";
9
10
  import { completions } from "./lsp/completions";
10
11
  import { hover } from "./lsp/hover";
11
12
  import { terraformConfigSchema, type TerraformConfig } from "./config";
12
13
  import { renderTerraformRoots } from "./hcl/roots";
13
- import { parseTerraformRootContent } from "./hcl/parse";
14
+ import { auditRootName, parseTerraformRootContent, RESOURCE_TYPE } from "./hcl/parse";
15
+ import { descendModules } from "./hcl/descend";
14
16
  import { TERRAFORM_STATE_OWNERSHIP_KEYS } from "./state-ownership";
15
17
 
16
18
  const loadSkills = createSkillsLoader(import.meta.url, [
@@ -22,6 +24,43 @@ const loadSkills = createSkillsLoader(import.meta.url, [
22
24
  },
23
25
  ]);
24
26
 
27
+ /**
28
+ * The only top-level names Terraform's JSON syntax admits
29
+ * (developer.hashicorp.com/terraform/language/syntax/json). Used as a
30
+ * whole-document test in `detectTemplate`: a `.tf.json` file has these keys
31
+ * and nothing else.
32
+ */
33
+ const TF_JSON_BLOCK_TYPES = new Set([
34
+ "terraform",
35
+ "provider",
36
+ "variable",
37
+ "output",
38
+ "locals",
39
+ "module",
40
+ "resource",
41
+ "data",
42
+ "moved",
43
+ "import",
44
+ "check",
45
+ "removed",
46
+ ]);
47
+
48
+ /**
49
+ * Whether raw text is HCL a Terraform root or module is written in. One
50
+ * top-level block header is enough, and every one of these headers is
51
+ * Terraform's own: `terraform {`, and the labelled `resource`/`data`
52
+ * (two labels), `provider`/`module`/`variable`/`output` (one label). `locals`
53
+ * is left out on purpose, since it is the one block a non-Terraform HCL
54
+ * dialect is also likely to carry.
55
+ */
56
+ function looksLikeHcl(text: string): boolean {
57
+ return (
58
+ /^[ \t]*terraform[ \t]*\{/m.test(text) ||
59
+ /^[ \t]*(resource|data)[ \t]+"[^"]+"[ \t]+"[^"]+"[ \t]*\{/m.test(text) ||
60
+ /^[ \t]*(provider|module|variable|output)[ \t]+"[^"]+"[ \t]*\{/m.test(text)
61
+ );
62
+ }
63
+
25
64
  /**
26
65
  * terraform lexicon plugin.
27
66
  *
@@ -70,11 +109,19 @@ export const terraformPlugin: LexiconPlugin = {
70
109
  // ── Optional extensions ────────────────────────────────────
71
110
 
72
111
  /**
73
- * Terraform's ownership channel is the state file, not a tag or a label
74
- * (#2087). Every address `terraform show -json` returns is `owned`;
75
- * everything else is `unknown`. Declaring the channel is what makes that a
76
- * claim the conformance suite checks rather than a silent degradation.
77
- * See `./describe-resources.ts` and `docs/pages/observation.mdx`.
112
+ * The ownership channel is per mode, and the channel declared here names
113
+ * the stock one, because a project's roots are stock unless it opts into
114
+ * choudoufu.
115
+ *
116
+ * On a stock root the channel is the state file, not a tag or a label
117
+ * (#2087): every address `terraform show -json` returns is `owned` and
118
+ * everything else is `unknown`. On a live root (#2104) it is choudoufu's
119
+ * two marker tags, `./live-ownership.ts`'s `TERRAFORM_LIVE_MARKER_KEYS`,
120
+ * and the verdicts come from `live-plan -json`'s own sections. Both
121
+ * readings resolve a real verdict on this path, which is what declaring
122
+ * `describeResources` here claims and what the conformance suite checks,
123
+ * for both modes. See `./describe-resources.ts` and
124
+ * `docs/pages/observation.mdx`.
78
125
  */
79
126
  ownershipChannel: {
80
127
  keys: TERRAFORM_STATE_OWNERSHIP_KEYS,
@@ -86,6 +133,26 @@ export const terraformPlugin: LexiconPlugin = {
86
133
  return describeResources(options);
87
134
  },
88
135
 
136
+ /**
137
+ * The kinds a live root can enumerate beyond its declaration (#1278,
138
+ * #2104). Empty on a project with no live root, because `live-ls` is the
139
+ * only estate-wide read this lexicon has and a stock root has none: a state
140
+ * file knows what it created and nothing else.
141
+ */
142
+ ambientKinds() {
143
+ return [RESOURCE_TYPE];
144
+ },
145
+
146
+ async observeAmbient(options) {
147
+ const { observeAmbient } = await import("./describe-resources");
148
+ return observeAmbient(options);
149
+ },
150
+
151
+ async teardownOwned(options) {
152
+ const { teardownOwned } = await import("./describe-resources");
153
+ return teardownOwned(options);
154
+ },
155
+
89
156
  /**
90
157
  * Each entry in `terraform.roots` is a root module directory that parses at
91
158
  * build time into one entity per HCL block. The render lives in
@@ -93,17 +160,28 @@ export const terraformPlugin: LexiconPlugin = {
93
160
  * project root, which is what relative `dir` paths resolve against.
94
161
  */
95
162
  async buildRoots(ctx): Promise<{ entities: Map<string, Declarable>; warnings: string[] }> {
96
- const roots = (ctx.config as { terraform?: TerraformConfig }).terraform?.roots ?? {};
163
+ const namespace = (ctx.config as { terraform?: TerraformConfig }).terraform;
164
+ const roots = namespace?.roots ?? {};
97
165
  if (Object.keys(roots).length === 0) return { entities: new Map(), warnings: [] };
98
- return renderTerraformRoots({ projectRoot: ctx.projectRoot, roots });
166
+ return renderTerraformRoots({
167
+ projectRoot: ctx.projectRoot,
168
+ roots,
169
+ binary: namespace?.binary,
170
+ callModuleType: namespace?.callModuleType,
171
+ });
99
172
  },
100
173
 
101
174
  lintRules() {
102
175
  return rules;
103
176
  },
104
177
 
178
+ /**
179
+ * The generated barrel of checks, each wrapped so a finding inside a
180
+ * descended child module names the call chain that reached it (#2112). See
181
+ * `./lint/post-synth/scope.ts`.
182
+ */
105
183
  postSynthChecks() {
106
- return postSynthChecks;
184
+ return withCallersChain(postSynthChecks);
107
185
  },
108
186
 
109
187
  auditCatalog() {
@@ -111,35 +189,114 @@ export const terraformPlugin: LexiconPlugin = {
111
189
  },
112
190
 
113
191
  /**
114
- * Parse-to-graph for `chant audit` (#1567, #2085). `content` is the
192
+ * `recommended`/`all` (chant #2113), the tflint-ruleset-terraform shape:
193
+ * `recommended` is every `merge-worthy` TF rule (a correctness/security
194
+ * finding worth reporting by default), `all` adds the `report-only` ones
195
+ * (hygiene the family grows into as #2109/#2110/#2112 land). Derived from
196
+ * `terraformAuditCatalog` rather than a hand-kept id list, so a rule that
197
+ * lands in a later, parallel issue is picked up automatically the moment
198
+ * its catalog entry ships, with no second list to fall out of sync.
199
+ */
200
+ lintPresets() {
201
+ const ids = Object.keys(terraformAuditCatalog);
202
+ const recommended = ids.filter((id) => terraformAuditCatalog[id].tier === "merge-worthy");
203
+ return { recommended, all: ids };
204
+ },
205
+
206
+ /**
207
+ * Parse-to-graph for `chant audit` (#1567, #2085, #2217). `content` is the
115
208
  * `# file: <name>`-joined bundle discovery builds for one discovered root
116
- * module (`classifyTerraform`, `packages/core/src/audit/core.ts`); the root
117
- * name itself isn't threaded through this hook's single-argument contract,
118
- * so a fixed placeholder ("audit-root") stands in for it. TF001 only uses
119
- * the root name to group and de-duplicate diagnostics within one parse, so
120
- * this is enough for the same graph-reading check that fires on `chant
121
- * build` to fire here too. Never throws: malformed HCL yields an empty map.
209
+ * module (`classifyTerraform`, `packages/core/src/audit/discover.ts`), and
210
+ * `input` says where that bundle came from.
211
+ *
212
+ * The scope is named after the input's path (`auditRootName`), so two roots
213
+ * in one repository are two roots here as well: they no longer collide in
214
+ * the merged entity map the all-files pass reads, which is what used to
215
+ * produce a finding keyed `<root>/<address>#2` against the file
216
+ * `(cross-file)` that neither directory deserved.
217
+ *
218
+ * When the audit walked a local filesystem, `input.dir` is the root
219
+ * module's directory and the parse descends its local `module` calls
220
+ * exactly as `buildRoots()` does, through the same `descendModules`
221
+ * (`../hcl/descend.ts`): a child module's entities are keyed
222
+ * `<root>/module.<name>/<address>` and carry the caller chain, so TF014 and
223
+ * TF015 fire here with the same `Callers:` line they print on a build, and
224
+ * the root-only rules (TF001 to TF003) still see only the root's own
225
+ * blocks. `input.baseDir` is the audited directory, which bounds the
226
+ * descent the way the project root bounds it on a build. Discovery drops a
227
+ * directory another one calls as a local module, so no module is audited
228
+ * twice.
229
+ *
230
+ * With no `input.dir` (a fetched repository; a caller parsing a bare
231
+ * string) the bundle is parsed alone, and the two child-module rules have
232
+ * no child to report on, since nothing named one.
233
+ *
234
+ * The descent's own refusals (a registry source, a source outside the
235
+ * audited tree, a cycle) are warnings on a build; this hook returns
236
+ * entities only, so on the audit path they are dropped rather than printed.
237
+ *
238
+ * Never throws: malformed HCL yields an empty map.
122
239
  */
123
- async auditEntities(content: string): Promise<Map<string, Declarable>> {
240
+ async auditEntities(content: string, input?: AuditEntitiesInput): Promise<Map<string, Declarable>> {
124
241
  try {
125
- return await parseTerraformRootContent(content, "audit-root");
242
+ const root = auditRootName(input?.path);
243
+ const entities = await parseTerraformRootContent(content, root);
244
+ if (input?.dir === undefined) return entities;
245
+ const { entities: children } = await descendModules(entities, {
246
+ dir: input.dir,
247
+ root,
248
+ projectRoot: input.baseDir ?? input.dir,
249
+ });
250
+ for (const [key, entity] of children) entities.set(key, entity);
251
+ return entities;
126
252
  } catch {
127
253
  return new Map();
128
254
  }
129
255
  },
130
256
 
257
+ /**
258
+ * No `mcpTools()` and no `mcpResources()`, deliberately (#2220). Both were
259
+ * declared returning `[]` behind a TODO, which passed the two tier-2
260
+ * "registers <method>" checks on a member that contributed nothing. Neither
261
+ * of the two shapes every other lexicon uses has anything to serve here:
262
+ * `createDiffTool` diffs one build's serializer output against the last,
263
+ * and this serializer writes nothing (`src/serializer.ts` is a deliberate
264
+ * no-op, the `.tf` files are the artifact); `createCatalogResource` serves
265
+ * the generated resource registry, and this lexicon generates none
266
+ * (`src/codegen/generate.ts` writes `{}` on purpose). So the members are
267
+ * removed rather than faked, and `chant dev check-lexicon` reports two
268
+ * tier-2 gaps that are real.
269
+ */
131
270
  skills: loadSkills,
132
271
 
133
- mcpTools() {
134
- return []; // TODO: Implement MCP tools
135
- },
136
-
137
- mcpResources() {
138
- return []; // TODO: Implement MCP resources
139
- },
140
-
141
- detectTemplate(_data: unknown) {
142
- return false; // TODO: Detect if a template belongs to this lexicon
272
+ /**
273
+ * `.tf` (and Terraform's own JSON syntax, `.tf.json`) recognised by content
274
+ * rather than by which directory a file sits in (#2220).
275
+ *
276
+ * Two input shapes, because the two callers hand over different things.
277
+ * `chant import` (`packages/core/src/cli/commands/import.ts`) `JSON.parse`s
278
+ * the file first, so it arrives as an object; the audit's content detection
279
+ * (`packages/core/src/audit/discover.ts`) hands azure its raw string, and a
280
+ * `.tf` file is not JSON at all, so the raw string form is the one that
281
+ * matters for HCL. Anything else reads as false.
282
+ *
283
+ * The object form is exact rather than heuristic: Terraform's JSON syntax
284
+ * admits only the top-level names in {@link TF_JSON_BLOCK_TYPES}, so a
285
+ * document is `.tf.json` when every key is one of them and at least one is
286
+ * present. That refuses a Kubernetes manifest, a CloudFormation template and
287
+ * a Compose file without having to name any of them.
288
+ *
289
+ * Discovery still classifies Terraform by the directory bundle
290
+ * (`classifyTerraform`), because a root module is a directory of files that
291
+ * has to be parsed together and one `.tf` on its own is not an audit input.
292
+ * This makes the per-file question answerable for the callers that ask it
293
+ * one file at a time.
294
+ */
295
+ detectTemplate(data: unknown) {
296
+ if (typeof data === "string") return looksLikeHcl(data);
297
+ if (typeof data !== "object" || data === null || Array.isArray(data)) return false;
298
+ const keys = Object.keys(data as Record<string, unknown>);
299
+ return keys.length > 0 && keys.every((k) => TF_JSON_BLOCK_TYPES.has(k));
143
300
  },
144
301
 
145
302
  completionProvider(ctx: CompletionContext) {
@@ -65,7 +65,7 @@ export const { op } = TerraformApplyOp({
65
65
  });
66
66
  ```
67
67
 
68
- `gate: "never"` is what makes the Op runnable under the local executor (`chant run`); any other mode emits a Gate phase, and a gate is durable-runtime work the local executor refuses. The Gate phase always shows the saved plan first and reports its `destroys` count as a `Destroys` search attribute, because `GateStep` carries no condition to branch on at build time — the approver sees what's at stake before approving, rather than the Op deciding for them.
68
+ `gate: "never"` drops the Gate phase, so `chant run` walks straight from Plan to Apply; any other mode emits a Gate phase, and a run that reaches an unapproved gate records a pending fact on the gate ledger, ends `gated` and exits 3 until someone runs `chant approve <op> <gate>`. The Gate phase always shows the saved plan first and reports its `destroys` count as a `Destroys` run outcome, because `GateStep` carries no condition to branch on at build time — the approver sees what's at stake before approving, rather than the Op deciding for them.
69
69
 
70
70
  Terraform has no automatic rollback, so `compensate: true` with no command throws at build time, naming the Op, rather than warning once an apply has already half-run:
71
71
 
package/src/validate.ts CHANGED
@@ -11,7 +11,16 @@ import { validateLexiconArtifacts, type ValidateResult } from "@intentius/chant/
11
11
 
12
12
  export type { ValidateCheck, ValidateResult } from "@intentius/chant/codegen/validate";
13
13
 
14
- // TODO: Add names of required entities for your lexicon
14
+ /**
15
+ * Empty, and correct empty (#2220). `requiredNames` names generated resource
16
+ * classes that must survive a regenerate, and this lexicon generates none:
17
+ * `codegen/generate.ts` writes `lexicon-terraform.json` as `{}` on purpose,
18
+ * because Terraform's resource surface lives in provider registries rather
19
+ * than in one upstream schema, and an estate's `.tf` files are read as they
20
+ * are. There is no name to require, so the list stays empty and the
21
+ * remaining checks (the artifacts exist, the registry parses, the surface
22
+ * snapshot) are what validate actually proves here.
23
+ */
15
24
  const REQUIRED_NAMES: string[] = [];
16
25
 
17
26
  /**