@intentius/chant-lexicon-terraform 0.124.52 → 0.124.55

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 (641) hide show
  1. package/README.md +14 -10
  2. package/dist/__fixtures__/pr-loop/estate.component.d.ts +7 -0
  3. package/dist/__fixtures__/pr-loop/estate.component.d.ts.map +1 -0
  4. package/dist/__fixtures__/pr-loop/estate.component.js +7 -0
  5. package/dist/__fixtures__/report/generate.d.ts +26 -0
  6. package/dist/__fixtures__/report/generate.d.ts.map +1 -0
  7. package/dist/__fixtures__/report/generate.js +58 -0
  8. package/dist/applied-state.d.ts +27 -0
  9. package/dist/applied-state.d.ts.map +1 -0
  10. package/dist/applied-state.js +44 -0
  11. package/dist/backend-config.d.ts +53 -0
  12. package/dist/backend-config.d.ts.map +1 -0
  13. package/dist/backend-config.js +136 -0
  14. package/dist/behaviour/kinds.d.ts.map +1 -1
  15. package/dist/behaviour/kinds.js +4 -0
  16. package/dist/behaviour/predict.d.ts.map +1 -1
  17. package/dist/behaviour/predict.js +6 -2
  18. package/dist/behaviour/reference-catalog.d.ts +16 -5
  19. package/dist/behaviour/reference-catalog.d.ts.map +1 -1
  20. package/dist/behaviour/reference-catalog.js +16 -5
  21. package/dist/behaviour/request.d.ts.map +1 -1
  22. package/dist/behaviour/request.js +3 -2
  23. package/dist/changed-roots.d.ts +6 -4
  24. package/dist/changed-roots.d.ts.map +1 -1
  25. package/dist/changed-roots.js +14 -18
  26. package/dist/ci-setup.d.ts +11 -0
  27. package/dist/ci-setup.d.ts.map +1 -0
  28. package/dist/ci-setup.js +41 -0
  29. package/dist/commands.d.ts.map +1 -1
  30. package/dist/commands.js +4 -3
  31. package/dist/components/capability-plugin.d.ts +105 -9
  32. package/dist/components/capability-plugin.d.ts.map +1 -1
  33. package/dist/components/capability-plugin.js +357 -36
  34. package/dist/components/compose-group.d.ts +145 -0
  35. package/dist/components/compose-group.d.ts.map +1 -0
  36. package/dist/components/compose-group.js +407 -0
  37. package/dist/components/placeholder-preview.d.ts +78 -0
  38. package/dist/components/placeholder-preview.d.ts.map +1 -0
  39. package/dist/components/placeholder-preview.js +254 -0
  40. package/dist/components/release-inputs.d.ts +29 -0
  41. package/dist/components/release-inputs.d.ts.map +1 -0
  42. package/dist/components/release-inputs.js +57 -0
  43. package/dist/components/trace-facts.d.ts +16 -0
  44. package/dist/components/trace-facts.d.ts.map +1 -0
  45. package/dist/components/trace-facts.js +19 -0
  46. package/dist/compose/apply-events.d.ts +46 -0
  47. package/dist/compose/apply-events.d.ts.map +1 -0
  48. package/dist/compose/apply-events.js +77 -0
  49. package/dist/compose/estate.d.ts +194 -0
  50. package/dist/compose/estate.d.ts.map +1 -0
  51. package/dist/compose/estate.js +363 -0
  52. package/dist/compose/hcl.d.ts +99 -0
  53. package/dist/compose/hcl.d.ts.map +1 -0
  54. package/dist/compose/hcl.js +302 -0
  55. package/dist/compose/module-address.d.ts +24 -0
  56. package/dist/compose/module-address.d.ts.map +1 -0
  57. package/dist/compose/module-address.js +43 -0
  58. package/dist/compose/root.d.ts +127 -0
  59. package/dist/compose/root.d.ts.map +1 -0
  60. package/dist/compose/root.js +586 -0
  61. package/dist/compose/run.d.ts +183 -0
  62. package/dist/compose/run.d.ts.map +1 -0
  63. package/dist/compose/run.js +447 -0
  64. package/dist/compose/slice.d.ts +102 -0
  65. package/dist/compose/slice.d.ts.map +1 -0
  66. package/dist/compose/slice.js +249 -0
  67. package/dist/compose/state-address.d.ts +28 -0
  68. package/dist/compose/state-address.d.ts.map +1 -0
  69. package/dist/compose/state-address.js +154 -0
  70. package/dist/compose/state-merge.d.ts +52 -0
  71. package/dist/compose/state-merge.d.ts.map +1 -0
  72. package/dist/compose/state-merge.js +135 -0
  73. package/dist/compose.d.ts +179 -0
  74. package/dist/compose.d.ts.map +1 -0
  75. package/dist/compose.js +482 -0
  76. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  77. package/dist/composites/terraform-apply-op.js +7 -3
  78. package/dist/cone.d.ts +53 -0
  79. package/dist/cone.d.ts.map +1 -0
  80. package/dist/cone.js +119 -0
  81. package/dist/config.d.ts +59 -13
  82. package/dist/config.d.ts.map +1 -1
  83. package/dist/config.js +43 -9
  84. package/dist/cost-hold.d.ts +27 -0
  85. package/dist/cost-hold.d.ts.map +1 -0
  86. package/dist/cost-hold.js +21 -0
  87. package/dist/describe-resources.d.ts +74 -12
  88. package/dist/describe-resources.d.ts.map +1 -1
  89. package/dist/describe-resources.js +374 -66
  90. package/dist/drift-read.d.ts +33 -0
  91. package/dist/drift-read.d.ts.map +1 -0
  92. package/dist/drift-read.js +243 -0
  93. package/dist/drift-record.d.ts +147 -0
  94. package/dist/drift-record.d.ts.map +1 -0
  95. package/dist/drift-record.js +383 -0
  96. package/dist/drift-rows.d.ts +61 -0
  97. package/dist/drift-rows.d.ts.map +1 -0
  98. package/dist/drift-rows.js +108 -0
  99. package/dist/exit-codes.d.ts +55 -0
  100. package/dist/exit-codes.d.ts.map +1 -0
  101. package/dist/exit-codes.js +48 -0
  102. package/dist/hcl/edges.d.ts.map +1 -1
  103. package/dist/hcl/edges.js +24 -1
  104. package/dist/hcl/files.d.ts +61 -0
  105. package/dist/hcl/files.d.ts.map +1 -0
  106. package/dist/hcl/files.js +221 -0
  107. package/dist/hcl/instances.d.ts +82 -0
  108. package/dist/hcl/instances.d.ts.map +1 -0
  109. package/dist/hcl/instances.js +390 -0
  110. package/dist/hcl/parse.d.ts +81 -9
  111. package/dist/hcl/parse.d.ts.map +1 -1
  112. package/dist/hcl/parse.js +114 -16
  113. package/dist/hcl/roots.d.ts.map +1 -1
  114. package/dist/hcl/roots.js +5 -0
  115. package/dist/hcl/suppressions.d.ts.map +1 -1
  116. package/dist/hcl/suppressions.js +8 -2
  117. package/dist/index.d.ts +13 -2
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +24 -2
  120. package/dist/integrity.json +8 -5
  121. package/dist/lint/audit-catalog.d.ts.map +1 -1
  122. package/dist/lint/audit-catalog.js +27 -3
  123. package/dist/lint/audit-lineage.d.ts +14 -2
  124. package/dist/lint/audit-lineage.d.ts.map +1 -1
  125. package/dist/lint/post-synth/index.d.ts.map +1 -1
  126. package/dist/lint/post-synth/index.js +4 -0
  127. package/dist/lint/post-synth/record-store.d.ts +54 -0
  128. package/dist/lint/post-synth/record-store.d.ts.map +1 -0
  129. package/dist/lint/post-synth/record-store.js +104 -0
  130. package/dist/lint/post-synth/tf001.d.ts +2 -2
  131. package/dist/lint/post-synth/tf001.js +2 -2
  132. package/dist/lint/post-synth/tf024.d.ts +35 -11
  133. package/dist/lint/post-synth/tf024.d.ts.map +1 -1
  134. package/dist/lint/post-synth/tf024.js +92 -20
  135. package/dist/lint/post-synth/tf046.d.ts +64 -0
  136. package/dist/lint/post-synth/tf046.d.ts.map +1 -0
  137. package/dist/lint/post-synth/tf046.js +175 -0
  138. package/dist/lint/post-synth/tf047.d.ts +36 -0
  139. package/dist/lint/post-synth/tf047.d.ts.map +1 -0
  140. package/dist/lint/post-synth/tf047.js +94 -0
  141. package/dist/live-plan-reasons.d.ts +21 -0
  142. package/dist/live-plan-reasons.d.ts.map +1 -0
  143. package/dist/live-plan-reasons.js +47 -0
  144. package/dist/lock-rows.d.ts +36 -0
  145. package/dist/lock-rows.d.ts.map +1 -0
  146. package/dist/lock-rows.js +60 -0
  147. package/dist/lock-timeout.d.ts +22 -0
  148. package/dist/lock-timeout.d.ts.map +1 -0
  149. package/dist/lock-timeout.js +32 -0
  150. package/dist/lsp/option-keys.d.ts.map +1 -1
  151. package/dist/lsp/option-keys.js +2 -0
  152. package/dist/manifest.json +1 -1
  153. package/dist/module-catalog/index.d.ts +69 -0
  154. package/dist/module-catalog/index.d.ts.map +1 -0
  155. package/dist/module-catalog/index.js +110 -0
  156. package/dist/module-catalog/refresh-cli.d.ts +4 -0
  157. package/dist/module-catalog/refresh-cli.d.ts.map +1 -0
  158. package/dist/module-catalog/refresh-cli.js +64 -0
  159. package/dist/module-catalog/static-tier.d.ts +6 -0
  160. package/dist/module-catalog/static-tier.d.ts.map +1 -0
  161. package/dist/module-catalog/static-tier.js +1650 -0
  162. package/dist/module-catalog/types.d.ts +65 -0
  163. package/dist/module-catalog/types.d.ts.map +1 -0
  164. package/dist/module-catalog/types.js +33 -0
  165. package/dist/okf/index.md +3 -1
  166. package/dist/okf/rules/TF024.md +2 -2
  167. package/dist/okf/rules/TF046.md +11 -0
  168. package/dist/okf/rules/TF047.md +11 -0
  169. package/dist/op/activities/index.d.ts +6 -2
  170. package/dist/op/activities/index.d.ts.map +1 -1
  171. package/dist/op/activities/index.js +5 -1
  172. package/dist/op/activities/live-detect.d.ts +4 -3
  173. package/dist/op/activities/live-detect.d.ts.map +1 -1
  174. package/dist/op/activities/live-detect.js +57 -32
  175. package/dist/op/activities/pin-rollout.d.ts +2 -0
  176. package/dist/op/activities/pin-rollout.d.ts.map +1 -1
  177. package/dist/op/activities/pin-rollout.js +1 -0
  178. package/dist/op/activities/terraform.d.ts +182 -5
  179. package/dist/op/activities/terraform.d.ts.map +1 -1
  180. package/dist/op/activities/terraform.js +224 -24
  181. package/dist/op/activity-contracts.d.ts +27 -0
  182. package/dist/op/activity-contracts.d.ts.map +1 -1
  183. package/dist/op/activity-contracts.js +19 -1
  184. package/dist/op/builders.d.ts +7 -1
  185. package/dist/op/builders.d.ts.map +1 -1
  186. package/dist/op/builders.js +9 -0
  187. package/dist/pin/consumers.d.ts +35 -0
  188. package/dist/pin/consumers.d.ts.map +1 -0
  189. package/dist/pin/consumers.js +70 -0
  190. package/dist/pin/edit.d.ts.map +1 -1
  191. package/dist/pin/edit.js +35 -0
  192. package/dist/pin/rollout.d.ts +2 -0
  193. package/dist/pin/rollout.d.ts.map +1 -1
  194. package/dist/pin/rollout.js +14 -11
  195. package/dist/plan-shape.d.ts +30 -0
  196. package/dist/plan-shape.d.ts.map +1 -0
  197. package/dist/plan-shape.js +125 -0
  198. package/dist/plugin.d.ts.map +1 -1
  199. package/dist/plugin.js +37 -18
  200. package/dist/registry/address.d.ts +63 -0
  201. package/dist/registry/address.d.ts.map +1 -0
  202. package/dist/registry/address.js +65 -0
  203. package/dist/registry/catalog-read.d.ts +14 -0
  204. package/dist/registry/catalog-read.d.ts.map +1 -0
  205. package/dist/registry/catalog-read.js +58 -0
  206. package/dist/registry/deprecated.d.ts +37 -0
  207. package/dist/registry/deprecated.d.ts.map +1 -0
  208. package/dist/registry/deprecated.js +43 -0
  209. package/dist/registry/docs.d.ts +96 -0
  210. package/dist/registry/docs.d.ts.map +1 -0
  211. package/dist/registry/docs.js +210 -0
  212. package/dist/registry/edge/edge.d.ts +46 -0
  213. package/dist/registry/edge/edge.d.ts.map +1 -0
  214. package/dist/registry/edge/edge.js +194 -0
  215. package/dist/registry/edge/index.d.ts +11 -0
  216. package/dist/registry/edge/index.d.ts.map +1 -0
  217. package/dist/registry/edge/index.js +10 -0
  218. package/dist/registry/edge/lambda-edge.d.ts +76 -0
  219. package/dist/registry/edge/lambda-edge.d.ts.map +1 -0
  220. package/dist/registry/edge/lambda-edge.js +95 -0
  221. package/dist/registry/edge/sigv4-web.d.ts +33 -0
  222. package/dist/registry/edge/sigv4-web.d.ts.map +1 -0
  223. package/dist/registry/edge/sigv4-web.js +56 -0
  224. package/dist/registry/edge/token-source.d.ts +73 -0
  225. package/dist/registry/edge/token-source.d.ts.map +1 -0
  226. package/dist/registry/edge/token-source.js +115 -0
  227. package/dist/registry/edge/worker.d.ts +45 -0
  228. package/dist/registry/edge/worker.d.ts.map +1 -0
  229. package/dist/registry/edge/worker.js +26 -0
  230. package/dist/registry/handler.d.ts +25 -0
  231. package/dist/registry/handler.d.ts.map +1 -0
  232. package/dist/registry/handler.js +75 -0
  233. package/dist/registry/headers-file.d.ts +21 -0
  234. package/dist/registry/headers-file.d.ts.map +1 -0
  235. package/dist/registry/headers-file.js +57 -0
  236. package/dist/registry/index.d.ts +31 -0
  237. package/dist/registry/index.d.ts.map +1 -0
  238. package/dist/registry/index.js +30 -0
  239. package/dist/registry/mirror-paths.d.ts +9 -0
  240. package/dist/registry/mirror-paths.d.ts.map +1 -0
  241. package/dist/registry/mirror-paths.js +8 -0
  242. package/dist/registry/mirror.d.ts +130 -0
  243. package/dist/registry/mirror.d.ts.map +1 -0
  244. package/dist/registry/mirror.js +319 -0
  245. package/dist/registry/proxies.d.ts +73 -0
  246. package/dist/registry/proxies.d.ts.map +1 -0
  247. package/dist/registry/proxies.js +111 -0
  248. package/dist/registry/reader.d.ts +57 -0
  249. package/dist/registry/reader.d.ts.map +1 -0
  250. package/dist/registry/reader.js +198 -0
  251. package/dist/registry/semver.d.ts +16 -0
  252. package/dist/registry/semver.d.ts.map +1 -0
  253. package/dist/registry/semver.js +24 -0
  254. package/dist/registry/serve.d.ts +24 -0
  255. package/dist/registry/serve.d.ts.map +1 -0
  256. package/dist/registry/serve.js +64 -0
  257. package/dist/registry/source-forms.d.ts +79 -0
  258. package/dist/registry/source-forms.d.ts.map +1 -0
  259. package/dist/registry/source-forms.js +202 -0
  260. package/dist/registry/targets/artifactory.d.ts +28 -0
  261. package/dist/registry/targets/artifactory.d.ts.map +1 -0
  262. package/dist/registry/targets/artifactory.js +206 -0
  263. package/dist/registry/targets/fake-artifactory.d.ts +30 -0
  264. package/dist/registry/targets/fake-artifactory.d.ts.map +1 -0
  265. package/dist/registry/targets/fake-artifactory.js +142 -0
  266. package/dist/registry/targets/fake-gitlab.d.ts +39 -0
  267. package/dist/registry/targets/fake-gitlab.d.ts.map +1 -0
  268. package/dist/registry/targets/fake-gitlab.js +136 -0
  269. package/dist/registry/targets/fake-hcp.d.ts +50 -0
  270. package/dist/registry/targets/fake-hcp.d.ts.map +1 -0
  271. package/dist/registry/targets/fake-hcp.js +202 -0
  272. package/dist/registry/targets/fake-http.d.ts +31 -0
  273. package/dist/registry/targets/fake-http.d.ts.map +1 -0
  274. package/dist/registry/targets/fake-http.js +87 -0
  275. package/dist/registry/targets/fake-nexus.d.ts +27 -0
  276. package/dist/registry/targets/fake-nexus.d.ts.map +1 -0
  277. package/dist/registry/targets/fake-nexus.js +137 -0
  278. package/dist/registry/targets/gitlab.d.ts +53 -0
  279. package/dist/registry/targets/gitlab.d.ts.map +1 -0
  280. package/dist/registry/targets/gitlab.js +187 -0
  281. package/dist/registry/targets/hcp.d.ts +29 -0
  282. package/dist/registry/targets/hcp.d.ts.map +1 -0
  283. package/dist/registry/targets/hcp.js +310 -0
  284. package/dist/registry/targets/hosted.d.ts +62 -0
  285. package/dist/registry/targets/hosted.d.ts.map +1 -0
  286. package/dist/registry/targets/hosted.js +74 -0
  287. package/dist/registry/targets/index.d.ts +18 -0
  288. package/dist/registry/targets/index.d.ts.map +1 -0
  289. package/dist/registry/targets/index.js +17 -0
  290. package/dist/registry/targets/module-target.d.ts +68 -0
  291. package/dist/registry/targets/module-target.d.ts.map +1 -0
  292. package/dist/registry/targets/module-target.js +63 -0
  293. package/dist/registry/targets/nexus.d.ts +43 -0
  294. package/dist/registry/targets/nexus.d.ts.map +1 -0
  295. package/dist/registry/targets/nexus.js +250 -0
  296. package/dist/registry/targets/zip.d.ts +55 -0
  297. package/dist/registry/targets/zip.d.ts.map +1 -0
  298. package/dist/registry/targets/zip.js +278 -0
  299. package/dist/registry/writer.d.ts +101 -0
  300. package/dist/registry/writer.d.ts.map +1 -0
  301. package/dist/registry/writer.js +109 -0
  302. package/dist/report.d.ts +15 -6
  303. package/dist/report.d.ts.map +1 -1
  304. package/dist/report.js +28 -55
  305. package/dist/rules/record-store.ts +122 -0
  306. package/dist/rules/tf001.ts +2 -2
  307. package/dist/rules/tf024.ts +105 -22
  308. package/dist/rules/tf046.ts +204 -0
  309. package/dist/rules/tf047.ts +114 -0
  310. package/dist/skills/chant-terraform.md +1 -1
  311. package/dist/state/azure-blob.d.ts +9 -0
  312. package/dist/state/azure-blob.d.ts.map +1 -0
  313. package/dist/state/azure-blob.js +8 -0
  314. package/dist/state/backend.d.ts +158 -0
  315. package/dist/state/backend.d.ts.map +1 -0
  316. package/dist/state/backend.js +406 -0
  317. package/dist/state/exec.d.ts +39 -0
  318. package/dist/state/exec.d.ts.map +1 -0
  319. package/dist/state/exec.js +80 -0
  320. package/dist/state/gcs.d.ts +8 -0
  321. package/dist/state/gcs.d.ts.map +1 -0
  322. package/dist/state/gcs.js +7 -0
  323. package/dist/state/index.d.ts +15 -0
  324. package/dist/state/index.d.ts.map +1 -0
  325. package/dist/state/index.js +14 -0
  326. package/dist/state/locks.d.ts +105 -0
  327. package/dist/state/locks.d.ts.map +1 -0
  328. package/dist/state/locks.js +130 -0
  329. package/dist/state/object-store.d.ts +8 -0
  330. package/dist/state/object-store.d.ts.map +1 -0
  331. package/dist/state/object-store.js +7 -0
  332. package/dist/state/root-states.d.ts +131 -0
  333. package/dist/state/root-states.d.ts.map +1 -0
  334. package/dist/state/root-states.js +241 -0
  335. package/dist/state/s3.d.ts +21 -0
  336. package/dist/state/s3.d.ts.map +1 -0
  337. package/dist/state/s3.js +19 -0
  338. package/dist/state/store.d.ts +51 -0
  339. package/dist/state/store.d.ts.map +1 -0
  340. package/dist/state/store.js +185 -0
  341. package/dist/state-ops.d.ts +255 -0
  342. package/dist/state-ops.d.ts.map +1 -0
  343. package/dist/state-ops.js +305 -0
  344. package/dist/state-reads.d.ts +57 -0
  345. package/dist/state-reads.d.ts.map +1 -0
  346. package/dist/state-reads.js +104 -0
  347. package/dist/terragrunt/affected.d.ts.map +1 -1
  348. package/dist/terragrunt/affected.js +2 -1
  349. package/dist/used-sources.d.ts +54 -0
  350. package/dist/used-sources.d.ts.map +1 -0
  351. package/dist/used-sources.js +157 -0
  352. package/package.json +24 -4
  353. package/src/__fixtures__/ACCEPTANCE.md +69 -7
  354. package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
  355. package/src/__fixtures__/compose/state-app.json +66 -0
  356. package/src/__fixtures__/compose/state-edge.json +40 -0
  357. package/src/__fixtures__/compose/state-net.json +58 -0
  358. package/src/__fixtures__/compose/three-app.plan.json +184 -0
  359. package/src/__fixtures__/compose/three-composed.plan.json +509 -0
  360. package/src/__fixtures__/compose/three-edge.plan.json +144 -0
  361. package/src/__fixtures__/compose/three-net.plan.json +174 -0
  362. package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
  363. package/src/__fixtures__/compose/working-after-apply.json +145 -0
  364. package/src/__fixtures__/config-blocks/app/main.tf +61 -0
  365. package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
  366. package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
  367. package/src/__fixtures__/config-files/shadow/main.tf +3 -0
  368. package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
  369. package/src/__fixtures__/config-files/shadow/other.tf +3 -0
  370. package/src/__fixtures__/config-files/tf/main.tf +11 -0
  371. package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
  372. package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
  373. package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
  374. package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
  375. package/src/__fixtures__/drift-record/README.md +16 -0
  376. package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
  377. package/src/__fixtures__/drift-rows/live-root-plan.json +70 -0
  378. package/src/__fixtures__/drift-rows/refresh-plan.json +61 -0
  379. package/src/__fixtures__/instance-forms/app/main.tf +110 -0
  380. package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
  381. package/src/__fixtures__/instance-vars/app/main.tf +52 -0
  382. package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
  383. package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
  384. package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
  385. package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
  386. package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
  387. package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
  388. package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
  389. package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
  390. package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
  391. package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
  392. package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
  393. package/src/__fixtures__/pr-loop/README.md +50 -0
  394. package/src/__fixtures__/pr-loop/chant.config.json +13 -0
  395. package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
  396. package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
  397. package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
  398. package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
  399. package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
  400. package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
  401. package/src/__fixtures__/report/applied.show.json +51 -0
  402. package/src/__fixtures__/report/generate.ts +63 -0
  403. package/src/__fixtures__/report/plan.show.json +200 -0
  404. package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
  405. package/src/__fixtures__/report/tf-apply/report.json +338 -0
  406. package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
  407. package/src/__fixtures__/report/tf-plan/report.json +320 -0
  408. package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
  409. package/src/__fixtures__/used-sources/root/extra.tofu +1 -0
  410. package/src/__fixtures__/used-sources/root/main.tf +31 -0
  411. package/src/__fixtures__/used-sources/root/modules/net/main.tf +14 -0
  412. package/src/__fixtures__/used-sources/root/modules/net/versions.tf.json +7 -0
  413. package/src/__fixtures__/used-sources/unit/terragrunt.hcl +3 -0
  414. package/src/applied-state.ts +52 -0
  415. package/src/backend-config.test.ts +105 -0
  416. package/src/backend-config.ts +133 -0
  417. package/src/behaviour/kinds.ts +4 -0
  418. package/src/behaviour/predict.ts +5 -2
  419. package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
  420. package/src/behaviour/reference-catalog.ts +16 -5
  421. package/src/behaviour/request.ts +3 -2
  422. package/src/change-set-instances.test.ts +98 -0
  423. package/src/changed-roots.ts +14 -16
  424. package/src/ci-setup.ts +42 -0
  425. package/src/commands.ts +4 -3
  426. package/src/components/capability-plugin.test.ts +198 -19
  427. package/src/components/capability-plugin.ts +383 -35
  428. package/src/components/change-set-graph.e2e.test.ts +11 -5
  429. package/src/components/compose-group.test.ts +486 -0
  430. package/src/components/compose-group.ts +489 -0
  431. package/src/components/demo-composed-roots.e2e.test.ts +440 -0
  432. package/src/components/member-outputs.test.ts +76 -0
  433. package/src/components/placeholder-preview.test.ts +147 -0
  434. package/src/components/placeholder-preview.ts +257 -0
  435. package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
  436. package/src/components/pr-loop.tofu.test.ts +113 -57
  437. package/src/components/release-inputs.ts +69 -0
  438. package/src/components/trace-facts.test.ts +21 -0
  439. package/src/components/trace-facts.ts +27 -0
  440. package/src/compose/apply-events.test.ts +62 -0
  441. package/src/compose/apply-events.ts +100 -0
  442. package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
  443. package/src/compose/estate.test.ts +421 -0
  444. package/src/compose/estate.ts +443 -0
  445. package/src/compose/hcl.ts +311 -0
  446. package/src/compose/module-address.ts +49 -0
  447. package/src/compose/root.test.ts +416 -0
  448. package/src/compose/root.ts +592 -0
  449. package/src/compose/run.test.ts +440 -0
  450. package/src/compose/run.tofu.test.ts +176 -0
  451. package/src/compose/run.ts +527 -0
  452. package/src/compose/slice.test.ts +184 -0
  453. package/src/compose/slice.ts +290 -0
  454. package/src/compose/state-address.ts +157 -0
  455. package/src/compose/state-merge.test.ts +131 -0
  456. package/src/compose/state-merge.ts +148 -0
  457. package/src/compose.test.ts +248 -0
  458. package/src/compose.ts +627 -0
  459. package/src/composites/terraform-apply-op.test.ts +9 -0
  460. package/src/composites/terraform-apply-op.ts +7 -3
  461. package/src/cone.ts +138 -0
  462. package/src/config-file-listers.test.ts +265 -0
  463. package/src/config.ts +44 -9
  464. package/src/cost-hold.ts +29 -0
  465. package/src/describe-resources.engine-facts.test.ts +411 -0
  466. package/src/describe-resources.live-module-keyed.test.ts +290 -0
  467. package/src/describe-resources.ts +416 -70
  468. package/src/drift-read.ts +284 -0
  469. package/src/drift-record.test.ts +223 -0
  470. package/src/drift-record.ts +455 -0
  471. package/src/drift-rows.test.ts +135 -0
  472. package/src/drift-rows.ts +137 -0
  473. package/src/exit-codes.test.ts +23 -0
  474. package/src/exit-codes.ts +59 -0
  475. package/src/hcl/config-blocks.test.ts +174 -0
  476. package/src/hcl/edges.ts +21 -1
  477. package/src/hcl/files.ts +234 -0
  478. package/src/hcl/instance-forms.test.ts +94 -0
  479. package/src/hcl/instances.test.ts +137 -0
  480. package/src/hcl/instances.ts +422 -0
  481. package/src/hcl/parse.test.ts +139 -1
  482. package/src/hcl/parse.ts +149 -16
  483. package/src/hcl/roots.ts +6 -0
  484. package/src/hcl/suppressions.ts +8 -2
  485. package/src/index.ts +79 -0
  486. package/src/lint/audit-catalog.ts +29 -3
  487. package/src/lint/audit-lineage.ts +14 -2
  488. package/src/lint/post-synth/fixtures/TF024/live-with-record-store/main.tf +18 -0
  489. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/estate.chdf.hcl +1 -0
  490. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/main.tf +10 -0
  491. package/src/lint/post-synth/fixtures/TF046/azurerm-no-resource-group/main.tf +15 -0
  492. package/src/lint/post-synth/fixtures/TF046/backend-config-only/main.tf +14 -0
  493. package/src/lint/post-synth/fixtures/TF046/derived-s3/backend.tf +7 -0
  494. package/src/lint/post-synth/fixtures/TF046/derived-s3/main.tf +9 -0
  495. package/src/lint/post-synth/fixtures/TF046/explicit-local/main.tf +16 -0
  496. package/src/lint/post-synth/fixtures/TF046/explicit-s3-beside-local-backend/main.tf +15 -0
  497. package/src/lint/post-synth/fixtures/TF046/implied-local/main.tf +9 -0
  498. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/estate.chdf.hcl +1 -0
  499. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/main.tf +5 -0
  500. package/src/lint/post-synth/fixtures/TF047/clear/main.tf +12 -0
  501. package/src/lint/post-synth/fixtures/TF047/refused/main.tf +12 -0
  502. package/src/lint/post-synth/fixtures/TF047/unknown-module/main.tf +12 -0
  503. package/src/lint/post-synth/fixtures/TF047/unknown-version/main.tf +12 -0
  504. package/src/lint/post-synth/index.ts +4 -0
  505. package/src/lint/post-synth/record-store.ts +122 -0
  506. package/src/lint/post-synth/tf001.ts +2 -2
  507. package/src/lint/post-synth/tf024.test.ts +26 -5
  508. package/src/lint/post-synth/tf024.ts +105 -22
  509. package/src/lint/post-synth/tf046.test.ts +137 -0
  510. package/src/lint/post-synth/tf046.ts +204 -0
  511. package/src/lint/post-synth/tf047.test.ts +56 -0
  512. package/src/lint/post-synth/tf047.ts +114 -0
  513. package/src/live-plan-reasons.ts +54 -0
  514. package/src/lock-rows.test.ts +70 -0
  515. package/src/lock-rows.ts +82 -0
  516. package/src/lock-timeout.ts +34 -0
  517. package/src/lsp/completions.test.ts +1 -1
  518. package/src/lsp/option-keys.ts +2 -0
  519. package/src/module-catalog/index.test.ts +93 -0
  520. package/src/module-catalog/index.ts +159 -0
  521. package/src/module-catalog/refresh-cli.ts +68 -0
  522. package/src/module-catalog/static-tier.ts +1659 -0
  523. package/src/module-catalog/types.ts +88 -0
  524. package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
  525. package/src/op/activities/index.ts +9 -0
  526. package/src/op/activities/live-detect.ts +55 -26
  527. package/src/op/activities/pin-rollout.ts +3 -0
  528. package/src/op/activities/registry.test.ts +2 -1
  529. package/src/op/activities/terraform.test.ts +55 -0
  530. package/src/op/activities/terraform.ts +312 -27
  531. package/src/op/activity-contracts.test.ts +1 -0
  532. package/src/op/activity-contracts.ts +24 -1
  533. package/src/op/builders.ts +14 -0
  534. package/src/op-wave-step.test.ts +109 -0
  535. package/src/pin/__fixtures__/consumers/live/dev/main.tf +4 -0
  536. package/src/pin/__fixtures__/consumers/live/prod/main.tf +21 -0
  537. package/src/pin/__fixtures__/consumers/units/vpc/terragrunt.hcl +7 -0
  538. package/src/pin/bundle.test.ts +2 -0
  539. package/src/pin/consumers.test.ts +57 -0
  540. package/src/pin/consumers.ts +95 -0
  541. package/src/pin/edit.ts +30 -0
  542. package/src/pin/rollout.ts +16 -11
  543. package/src/plan-shape.test.ts +148 -0
  544. package/src/plan-shape.ts +128 -0
  545. package/src/plugin.ts +39 -19
  546. package/src/registry/__fixtures__/docs/documented/.terraform-docs.yml +3 -0
  547. package/src/registry/__fixtures__/docs/documented/README.md +1 -0
  548. package/src/registry/__fixtures__/docs/documented/main.tf +8 -0
  549. package/src/registry/__fixtures__/docs/json/main.tf.json +14 -0
  550. package/src/registry/__fixtures__/docs/vpc/README.md +3 -0
  551. package/src/registry/__fixtures__/docs/vpc/examples/complete/main.tf +5 -0
  552. package/src/registry/__fixtures__/docs/vpc/examples/simple/README.md +1 -0
  553. package/src/registry/__fixtures__/docs/vpc/examples/simple/main.tf +4 -0
  554. package/src/registry/__fixtures__/docs/vpc/main.tf +14 -0
  555. package/src/registry/__fixtures__/docs/vpc/outputs.tf +9 -0
  556. package/src/registry/__fixtures__/docs/vpc/variables.tf +16 -0
  557. package/src/registry/__fixtures__/proxied-root/main.tf +12 -0
  558. package/src/registry/address.ts +100 -0
  559. package/src/registry/catalog-read.ts +56 -0
  560. package/src/registry/deprecated.test.ts +139 -0
  561. package/src/registry/deprecated.ts +76 -0
  562. package/src/registry/docs.test.ts +140 -0
  563. package/src/registry/docs.ts +299 -0
  564. package/src/registry/edge/bundle.test.ts +50 -0
  565. package/src/registry/edge/edge.test.ts +176 -0
  566. package/src/registry/edge/edge.ts +211 -0
  567. package/src/registry/edge/index.ts +10 -0
  568. package/src/registry/edge/lambda-edge.ts +127 -0
  569. package/src/registry/edge/sigv4-web.ts +79 -0
  570. package/src/registry/edge/token-source.test.ts +258 -0
  571. package/src/registry/edge/token-source.ts +169 -0
  572. package/src/registry/edge/worker.ts +61 -0
  573. package/src/registry/handler.ts +89 -0
  574. package/src/registry/headers-file.ts +61 -0
  575. package/src/registry/index.ts +31 -0
  576. package/src/registry/mirror-paths.ts +10 -0
  577. package/src/registry/mirror.test.ts +198 -0
  578. package/src/registry/mirror.ts +395 -0
  579. package/src/registry/proxies.test.ts +153 -0
  580. package/src/registry/proxies.ts +132 -0
  581. package/src/registry/reader.ts +225 -0
  582. package/src/registry/registry.test.ts +262 -0
  583. package/src/registry/semver.ts +34 -0
  584. package/src/registry/serve.ts +82 -0
  585. package/src/registry/source-forms.test.ts +87 -0
  586. package/src/registry/source-forms.ts +216 -0
  587. package/src/registry/targets/artifactory.test.ts +248 -0
  588. package/src/registry/targets/artifactory.ts +257 -0
  589. package/src/registry/targets/fake-artifactory.ts +158 -0
  590. package/src/registry/targets/fake-gitlab.ts +161 -0
  591. package/src/registry/targets/fake-hcp.ts +233 -0
  592. package/src/registry/targets/fake-http.ts +97 -0
  593. package/src/registry/targets/fake-nexus.ts +152 -0
  594. package/src/registry/targets/gitlab.test.ts +208 -0
  595. package/src/registry/targets/gitlab.ts +223 -0
  596. package/src/registry/targets/hcp.test.ts +230 -0
  597. package/src/registry/targets/hcp.ts +360 -0
  598. package/src/registry/targets/hosted.ts +117 -0
  599. package/src/registry/targets/index.ts +17 -0
  600. package/src/registry/targets/module-target.ts +131 -0
  601. package/src/registry/targets/nexus.test.ts +220 -0
  602. package/src/registry/targets/nexus.ts +314 -0
  603. package/src/registry/targets/zip.test.ts +65 -0
  604. package/src/registry/targets/zip.ts +287 -0
  605. package/src/registry/writer.ts +155 -0
  606. package/src/report.test.ts +82 -1
  607. package/src/report.ts +31 -54
  608. package/src/signal-only-root.test.ts +72 -0
  609. package/src/skills/chant-terraform.md +1 -1
  610. package/src/state/azure-blob.ts +8 -0
  611. package/src/state/backend-verdict.test.ts +187 -0
  612. package/src/state/backend.test.ts +127 -0
  613. package/src/state/backend.ts +488 -0
  614. package/src/state/exec.ts +111 -0
  615. package/src/state/gcs.ts +7 -0
  616. package/src/state/index.ts +15 -0
  617. package/src/state/locks.test.ts +84 -0
  618. package/src/state/locks.ts +201 -0
  619. package/src/state/object-store.ts +7 -0
  620. package/src/state/root-states-floci.e2e.test.ts +66 -0
  621. package/src/state/root-states.test.ts +462 -0
  622. package/src/state/root-states.ts +309 -0
  623. package/src/state/s3.ts +21 -0
  624. package/src/state/store.ts +225 -0
  625. package/src/state-ops.test.ts +182 -0
  626. package/src/state-ops.ts +462 -0
  627. package/src/state-reads.ts +116 -0
  628. package/src/terragrunt/affected.ts +2 -1
  629. package/src/used-sources.test.ts +75 -0
  630. package/src/used-sources.ts +204 -0
  631. package/src/workspace-kinds.test.ts +4 -17
  632. package/workspace-kinds.json +1 -24
  633. package/dist/workspace-member.d.ts +0 -30
  634. package/dist/workspace-member.d.ts.map +0 -1
  635. package/dist/workspace-member.js +0 -91
  636. package/schemas/pulumi.schema.json +0 -34
  637. package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
  638. package/src/testdata/pulumi-member/stack-export.json +0 -53
  639. package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
  640. package/src/workspace-member.test.ts +0 -89
  641. package/src/workspace-member.ts +0 -126
@@ -0,0 +1,137 @@
1
+ import { dirname, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { describe, expect, test } from "vitest";
4
+ import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
5
+ import { applyInlineSuppressions } from "@intentius/chant/lint/suppressions";
6
+ import { ALLOW_LOCAL_RECORD_STORE_ENV, checkLocalRecordStore, isCiEnvironment } from "./tf046";
7
+ import { tf024 } from "./tf024";
8
+ import { blocksToEntities, parseTerraformRootDir } from "../../hcl/parse";
9
+
10
+ const fixtures = join(dirname(fileURLToPath(import.meta.url)), "fixtures", "TF046");
11
+
12
+ const CI = { CI: "true" };
13
+
14
+ async function ctxFor(fixture: string, binary: "choudoufu" | "terraform" = "choudoufu"): Promise<PostSynthContext> {
15
+ const entities = await parseTerraformRootDir(join(fixtures, fixture), "estate", undefined, { binary });
16
+ return { outputs: new Map(), entities } as unknown as PostSynthContext;
17
+ }
18
+
19
+ describe("TF046: live root resolves to the local record store in CI", () => {
20
+ test("fires on a live root with no record_store and no backend", async () => {
21
+ const diags = checkLocalRecordStore(await ctxFor("implied-local"), CI);
22
+ expect(diags).toHaveLength(1);
23
+ expect(diags[0].checkId).toBe("TF046");
24
+ expect(diags[0].severity).toBe("error");
25
+ expect(diags[0].entity).toBe("estate/terraform");
26
+ expect(diags[0].message).toContain("no backend block to derive one from");
27
+ expect(diags[0].message).toContain("declare a record_store");
28
+ expect(diags[0].message).toContain('backend "s3"');
29
+ });
30
+
31
+ test("stays quiet when an ignored s3 backend with a literal bucket gives a derived store", async () => {
32
+ // The backend sits in its own backend.tf, as most roots keep it.
33
+ expect(checkLocalRecordStore(await ctxFor("derived-s3"), CI)).toHaveLength(0);
34
+ });
35
+
36
+ test("agrees with TF024 on the derived store", async () => {
37
+ const ctx = await ctxFor("derived-s3");
38
+ const diags = tf024.check(ctx);
39
+ expect(diags).toHaveLength(1);
40
+ expect(diags[0].message).toContain("derives the record store");
41
+ });
42
+
43
+ test("fires when the backend's bucket comes only from -backend-config, and says so", async () => {
44
+ const ctx = await ctxFor("backend-config-only");
45
+ const diags = checkLocalRecordStore(ctx, CI);
46
+ expect(diags).toHaveLength(1);
47
+ expect(diags[0].message).toContain('backend "s3" sets no literal bucket');
48
+ // TF024 gives the same reason instead of claiming a derived store.
49
+ const tf024Diags = tf024.check(ctx);
50
+ expect(tf024Diags[0].message).toContain("sets no literal bucket");
51
+ expect(tf024Diags[0].message).not.toContain("derives the record store");
52
+ });
53
+
54
+ test("fires on an azurerm backend that leaves out resource_group_name", async () => {
55
+ const diags = checkLocalRecordStore(await ctxFor("azurerm-no-resource-group"), CI);
56
+ expect(diags).toHaveLength(1);
57
+ expect(diags[0].message).toContain("resource_group_name");
58
+ });
59
+
60
+ test('fires on an explicit record_store "local", even beside a derivable backend', async () => {
61
+ const diags = checkLocalRecordStore(await ctxFor("explicit-local"), CI);
62
+ expect(diags).toHaveLength(1);
63
+ expect(diags[0].message).toContain('record_store "local"');
64
+ });
65
+
66
+ test("stays quiet on an explicit bucket record_store beside a local backend", async () => {
67
+ expect(checkLocalRecordStore(await ctxFor("explicit-s3-beside-local-backend"), CI)).toHaveLength(0);
68
+ });
69
+
70
+ test("keys on an estate.chdf.hcl sidecar as well as a live block", async () => {
71
+ const diags = checkLocalRecordStore(await ctxFor("sidecar-implied"), CI);
72
+ expect(diags).toHaveLength(1);
73
+ expect(diags[0].entity).toMatch(/^estate\/live/);
74
+ });
75
+
76
+ test("a sidecar estate opts out with chant-ignore-file in estate.chdf.hcl", async () => {
77
+ const entities = await blocksToEntities(
78
+ [{ name: "main.tf", source: "terraform {}\n" }],
79
+ "app",
80
+ undefined,
81
+ { binary: "choudoufu" },
82
+ { name: "estate.chdf.hcl", source: '# chant-ignore-file: TF046\nestate = "e"\n' },
83
+ );
84
+ const ctx = { outputs: new Map(), entities } as unknown as PostSynthContext;
85
+ const diags = checkLocalRecordStore(ctx, CI);
86
+ expect(diags).toHaveLength(1);
87
+ expect(applyInlineSuppressions(diags, ctx.entities).diagnostics).toHaveLength(0);
88
+ });
89
+
90
+ test("says nothing outside CI", async () => {
91
+ expect(checkLocalRecordStore(await ctxFor("implied-local"), {})).toHaveLength(0);
92
+ expect(checkLocalRecordStore(await ctxFor("implied-local"), { CI: "false" })).toHaveLength(0);
93
+ });
94
+
95
+ test("an emulator or test lane opts out through the environment", async () => {
96
+ const env = { ...CI, [ALLOW_LOCAL_RECORD_STORE_ENV]: "1" };
97
+ expect(checkLocalRecordStore(await ctxFor("explicit-local"), env)).toHaveLength(0);
98
+ expect(checkLocalRecordStore(await ctxFor("implied-local"), env)).toHaveLength(0);
99
+ });
100
+
101
+ test("one root opts out with chant-ignore-block above the terraform block holding live", async () => {
102
+ const source =
103
+ '# chant-ignore-block: TF046\nterraform {\n live {\n estate = "e"\n record_store "local" {}\n }\n}\n';
104
+ const entities = await blocksToEntities([{ name: "main.tf", source }], "app", undefined, { binary: "choudoufu" });
105
+ const ctx = { outputs: new Map(), entities } as unknown as PostSynthContext;
106
+ const diags = checkLocalRecordStore(ctx, CI);
107
+ expect(diags).toHaveLength(1);
108
+ expect(applyInlineSuppressions(diags, ctx.entities).diagnostics).toHaveLength(0);
109
+ });
110
+
111
+ test("stays quiet on a Terragrunt unit whose remote_state writes the backend at run time", async () => {
112
+ const files = [
113
+ { name: "main.tf", source: 'terraform {\n live {\n estate = "e"\n }\n}\n' },
114
+ {
115
+ name: "terragrunt.hcl",
116
+ source: 'remote_state {\n backend = "s3"\n generate = {\n path = "backend.tf"\n if_exists = "overwrite"\n }\n config = {\n bucket = "tfstate"\n }\n}\n',
117
+ },
118
+ ];
119
+ const entities = await blocksToEntities(files, "unit", undefined, { binary: "choudoufu" });
120
+ const ctx = { outputs: new Map(), entities } as unknown as PostSynthContext;
121
+ expect(checkLocalRecordStore(ctx, CI)).toHaveLength(0);
122
+ });
123
+
124
+ test("does not fire on a stock root", async () => {
125
+ expect(checkLocalRecordStore(await ctxFor("implied-local", "terraform"), CI)).toHaveLength(0);
126
+ });
127
+
128
+ test("reads CI from the variables each forge sets", () => {
129
+ expect(isCiEnvironment({ CI: "1" })).toBe(true);
130
+ expect(isCiEnvironment({ GITHUB_ACTIONS: "true" })).toBe(true);
131
+ expect(isCiEnvironment({ GITLAB_CI: "true" })).toBe(true);
132
+ expect(isCiEnvironment({ FORGEJO_ACTIONS: "true" })).toBe(true);
133
+ expect(isCiEnvironment({ TF_BUILD: "True" })).toBe(true);
134
+ expect(isCiEnvironment({ CI: "0" })).toBe(false);
135
+ expect(isCiEnvironment({})).toBe(false);
136
+ });
137
+ });
@@ -0,0 +1,204 @@
1
+ /**
2
+ * TF046: a live root's record store is local, and the lint runs in CI.
3
+ *
4
+ * A live root keeps its records (what binds a record-backed resource to the
5
+ * estate) in a record store. With nothing to say otherwise that store is a
6
+ * directory beside the module (choudoufu `live/STORAGE.md`), and on a CI
7
+ * runner the directory is empty on every run: an ordinary resource still binds
8
+ * by its marker, but a record-backed one is proposed for create again.
9
+ *
10
+ * "Local" is what choudoufu resolves, read here through `./record-store.ts`,
11
+ * the same resolution TF024 names in its message (INTENTIUS/choudoufu#2293):
12
+ *
13
+ * - an explicit `record_store "local"` fires;
14
+ * - an explicit `record_store` of any other kind is quiet, whatever backend
15
+ * sits beside it;
16
+ * - with no `record_store`, an ignored `backend "s3"`, `"gcs"` or `"azurerm"`
17
+ * block that names its bucket as a literal gives a derived store and is
18
+ * quiet;
19
+ * - with no `record_store` and no backend, or a backend with no counterpart
20
+ * (`local`, `consul`, `http`), or one whose bucket comes only from
21
+ * `-backend-config`, the store is implied local and the rule fires, naming
22
+ * the reason choudoufu would print.
23
+ *
24
+ * A root that is also a Terragrunt unit with a `remote_state`, a `generate`
25
+ * block that writes a backend, or an `include` of a parent config gets its
26
+ * backend file at run time, so what chant reads is not what choudoufu sees.
27
+ * The rule stays quiet there rather than guess; TF044 still reports a
28
+ * Terragrunt `backend = "local"`.
29
+ *
30
+ * CI is read from the environment chant runs in: `CI` set to anything but
31
+ * `false` or `0`, or `GITHUB_ACTIONS`, `GITLAB_CI`, `FORGEJO_ACTIONS`,
32
+ * `GITEA_ACTIONS` or `TF_BUILD` (Azure Pipelines) set to `true`. Outside CI the
33
+ * local store is a reasonable choice for one person or a demo, and the rule
34
+ * says nothing.
35
+ *
36
+ * Opt-outs, for emulator and test lanes that use the local store on purpose:
37
+ * `CHANT_ALLOW_LOCAL_RECORD_STORE=1` in the lane's environment, or
38
+ * `# chant-ignore-block: TF046` above the `terraform` block holding `live`
39
+ * for one root, or
40
+ * `lint.rules: { TF046: "off" }` for a whole project.
41
+ *
42
+ * The rule is `report-only` in the audit catalog, so it is off under the
43
+ * `recommended` preset: terragucci switches it on and holds the product
44
+ * wording (chant#4561 placement).
45
+ *
46
+ * One diagnostic per root. It fires from the `terraform` block that holds the
47
+ * `live` block, since suppression comments anchor on top-level blocks only,
48
+ * and from the sidecar's `Terraform::Live` entity for a sidecar estate, where
49
+ * `# chant-ignore-file: TF046` in `estate.chdf.hcl` suppresses it.
50
+ */
51
+
52
+ import type {
53
+ PostSynthCheck,
54
+ PostSynthContext,
55
+ PostSynthDiagnostic,
56
+ } from "@intentius/chant/lint/post-synth";
57
+ import { isResourceDeclarable, type ResourceDeclarable } from "@intentius/chant/declarable";
58
+ import {
59
+ LIVE_TYPE,
60
+ TERRAFORM_TYPE,
61
+ TERRAGRUNT_CONFIG_TYPE,
62
+ TERRAGRUNT_GENERATE_TYPE,
63
+ TERRAGRUNT_REMOTE_STATE_TYPE,
64
+ type BlockBody,
65
+ } from "../../hcl/parse";
66
+ import { isRootScoped } from "./scope";
67
+ import { resolveRecordStore, type ResolvedRecordStore } from "./record-store";
68
+
69
+ /** The environment variable an emulator or test lane sets to keep the local store without a finding. */
70
+ export const ALLOW_LOCAL_RECORD_STORE_ENV = "CHANT_ALLOW_LOCAL_RECORD_STORE";
71
+
72
+ type Env = Readonly<Record<string, string | undefined>>;
73
+
74
+ const truthy = (v: string | undefined): boolean => v !== undefined && v !== "" && v !== "0" && v.toLowerCase() !== "false";
75
+
76
+ /** Whether the environment is a CI job. */
77
+ export function isCiEnvironment(env: Env): boolean {
78
+ if (truthy(env.CI)) return true;
79
+ for (const name of ["GITHUB_ACTIONS", "GITLAB_CI", "FORGEJO_ACTIONS", "GITEA_ACTIONS", "TF_BUILD"]) {
80
+ if (env[name]?.toLowerCase() === "true") return true;
81
+ }
82
+ return false;
83
+ }
84
+
85
+ const bodyOf = (entity: ResourceDeclarable): BlockBody => {
86
+ const props = entity.props as { body?: unknown };
87
+ return (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
88
+ };
89
+
90
+ const rootOf = (entity: ResourceDeclarable): string => {
91
+ const root = (entity.props as { root?: unknown }).root;
92
+ return typeof root === "string" ? root : "";
93
+ };
94
+
95
+ interface LiveRoot {
96
+ /** Entity key the finding anchors on: the live declaration's entity, until a `terraform` block holding it is seen. */
97
+ anchor: string;
98
+ liveBodies: BlockBody[];
99
+ terraformBodies: BlockBody[];
100
+ /** A Terragrunt unit whose backend file is written at run time. */
101
+ runtimeBackend: boolean;
102
+ }
103
+
104
+ /** Whether a Terragrunt block in this root writes a backend file at run time. */
105
+ function writesRuntimeBackend(entity: ResourceDeclarable): boolean {
106
+ const body = bodyOf(entity);
107
+ switch (entity.entityType) {
108
+ case TERRAGRUNT_REMOTE_STATE_TYPE:
109
+ return body.backend !== "local";
110
+ case TERRAGRUNT_GENERATE_TYPE: {
111
+ const contents = typeof body.contents === "string" ? body.contents : "";
112
+ return /\bbackend\s+"(?!local")/.test(contents);
113
+ }
114
+ case TERRAGRUNT_CONFIG_TYPE:
115
+ return Array.isArray(body.includes) && body.includes.length > 0;
116
+ default:
117
+ return false;
118
+ }
119
+ }
120
+
121
+ function liveRoots(ctx: PostSynthContext): Map<string, LiveRoot> {
122
+ const roots = new Map<string, LiveRoot>();
123
+ const terraform = new Map<string, BlockBody[]>();
124
+ /** Per root, the key of the first `terraform` block that holds a `live` block. */
125
+ const holder = new Map<string, string>();
126
+ const runtime = new Set<string>();
127
+
128
+ for (const [key, entity] of ctx.entities) {
129
+ if (!isResourceDeclarable(entity) || !isRootScoped(entity)) continue;
130
+ const root = rootOf(entity);
131
+ if (entity.entityType === TERRAFORM_TYPE) {
132
+ const list = terraform.get(root) ?? [];
133
+ const body = bodyOf(entity);
134
+ list.push(body);
135
+ terraform.set(root, list);
136
+ if (body.live !== undefined && !holder.has(root)) holder.set(root, key);
137
+ continue;
138
+ }
139
+ if (entity.entityType === LIVE_TYPE) {
140
+ if ((entity.props as { mode?: unknown }).mode !== "live") continue;
141
+ const found = roots.get(root);
142
+ if (found) found.liveBodies.push(bodyOf(entity));
143
+ else roots.set(root, { anchor: key, liveBodies: [bodyOf(entity)], terraformBodies: [], runtimeBackend: false });
144
+ continue;
145
+ }
146
+ if (writesRuntimeBackend(entity)) runtime.add(root);
147
+ }
148
+
149
+ for (const [root, live] of roots) {
150
+ live.terraformBodies = terraform.get(root) ?? [];
151
+ live.anchor = holder.get(root) ?? live.anchor;
152
+ live.runtimeBackend = runtime.has(root);
153
+ }
154
+ return roots;
155
+ }
156
+
157
+ function message(root: string, store: ResolvedRecordStore): string {
158
+ const why =
159
+ store.source === "record_store"
160
+ ? 'Its live declaration names record_store "local", a directory beside the module.'
161
+ : store.note
162
+ ? `It declares no record_store, and no store is derived from its backend: ${store.note}.`
163
+ : store.backend
164
+ ? `It declares no record_store, and its backend "${store.backend}" block keeps state on local disk, so choudoufu uses the local store.`
165
+ : "It declares no record_store and has no backend block to derive one from, so choudoufu uses the local store.";
166
+ return (
167
+ `Live root "${root}" keeps its records in the local store, and this is a CI run. ${why} ` +
168
+ "A CI runner starts with an empty directory every run, so each record-backed resource is proposed " +
169
+ "for create again. Keep or add a backend \"s3\", \"gcs\" or \"azurerm\" block with a literal bucket " +
170
+ "(choudoufu derives the store from it), or declare a record_store in the live block or " +
171
+ `estate.chdf.hcl. A lane that uses the local store on purpose sets ${ALLOW_LOCAL_RECORD_STORE_ENV}=1.`
172
+ );
173
+ }
174
+
175
+ /**
176
+ * TF046 against an explicit environment. `tf046.check` passes `process.env`;
177
+ * tests pass their own.
178
+ */
179
+ export function checkLocalRecordStore(ctx: PostSynthContext, env: Env): PostSynthDiagnostic[] {
180
+ if (!isCiEnvironment(env) || truthy(env[ALLOW_LOCAL_RECORD_STORE_ENV])) return [];
181
+ const diagnostics: PostSynthDiagnostic[] = [];
182
+ for (const [root, live] of liveRoots(ctx)) {
183
+ if (live.runtimeBackend) continue;
184
+ const store = resolveRecordStore(live.terraformBodies, live.liveBodies);
185
+ if (store.kind !== "local") continue;
186
+ diagnostics.push({
187
+ checkId: "TF046",
188
+ severity: "error",
189
+ message: message(root, store),
190
+ entity: live.anchor,
191
+ lexicon: "terraform",
192
+ });
193
+ }
194
+ return diagnostics;
195
+ }
196
+
197
+ export const tf046: PostSynthCheck = {
198
+ id: "TF046",
199
+ description: "Live root resolves to the local record store in a CI run",
200
+
201
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
202
+ return checkLocalRecordStore(ctx, process.env);
203
+ },
204
+ };
@@ -0,0 +1,56 @@
1
+ import { dirname, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { describe, expect, test } from "vitest";
4
+ import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
5
+ import { parseTerraformRootDir } from "../../hcl/parse";
6
+ import { terraformPlugin } from "../../plugin";
7
+ import { tf047 } from "./tf047";
8
+
9
+ const fixtures = join(dirname(fileURLToPath(import.meta.url)), "fixtures", "TF047");
10
+
11
+ async function ctxFor(fixture: string, binary: "choudoufu" | "terraform" = "choudoufu"): Promise<PostSynthContext> {
12
+ const entities = await parseTerraformRootDir(join(fixtures, fixture), "estate", undefined, { binary });
13
+ return { outputs: new Map(), entities } as unknown as PostSynthContext;
14
+ }
15
+
16
+ describe("TF047: live root pins a module version the catalog has no row for, or one with refusals", () => {
17
+ test("warns on a version whose static rows are refused, with IDs, links and the tier", async () => {
18
+ const diags = tf047.check(await ctxFor("refused"));
19
+ expect(diags).toHaveLength(1);
20
+ expect(diags[0]).toMatchObject({ checkId: "TF047", severity: "warning", entity: "estate/module.eks" });
21
+ expect(diags[0].message).toContain("terraform-aws-modules/eks/aws 21.29.0");
22
+ expect(diags[0].message).toContain("6 of 6 examples");
23
+ expect(diags[0].message).toContain("static tier");
24
+ expect(diags[0].message).toContain("https://github.com/INTENTIUS/choudoufu/blob/main/live/LIMITATIONS.md#");
25
+ expect(diags[0].message).not.toMatch(/gauntlet/i);
26
+ });
27
+
28
+ test("is quiet when every example at the version is clear", async () => {
29
+ expect(tf047.check(await ctxFor("clear"))).toEqual([]);
30
+ });
31
+
32
+ test("reports a version with no row at info, naming the measured versions", async () => {
33
+ const diags = tf047.check(await ctxFor("unknown-version"));
34
+ expect(diags).toHaveLength(1);
35
+ expect(diags[0].severity).toBe("info");
36
+ expect(diags[0].message).toContain("no row at this version");
37
+ expect(diags[0].message).toContain("6.6.1");
38
+ });
39
+
40
+ test("reports a module with no row at info", async () => {
41
+ const diags = tf047.check(await ctxFor("unknown-module"));
42
+ expect(diags).toHaveLength(1);
43
+ expect(diags[0].severity).toBe("info");
44
+ expect(diags[0].message).toContain("no row for this module");
45
+ });
46
+
47
+ test("skips a stock root: the table measures choudoufu", async () => {
48
+ expect(tf047.check(await ctxFor("refused", "terraform"))).toEqual([]);
49
+ });
50
+
51
+ test("is off by default: in the all preset, not in recommended", () => {
52
+ const presets = terraformPlugin.lintPresets!();
53
+ expect(presets.all).toContain("TF047");
54
+ expect(presets.recommended).not.toContain("TF047");
55
+ });
56
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * TF047: a live root pins a registry module version the known-module catalog
3
+ * has no row for, or one whose row carries refusals.
4
+ *
5
+ * The catalog is choudoufu's module compatibility table, vendored in
6
+ * `../../module-catalog/` (chant#4560, choudoufu#2292). It has one static
7
+ * row per terraform-aws-modules example at one tag, measured offline in the
8
+ * onboarded form. A module call on a root whose binary is choudoufu (the
9
+ * root's `mode` is `live`) with an exact `version` is looked up there:
10
+ *
11
+ * - no row for the module, or none at the pinned version: `info`, naming the
12
+ * versions the static tier did measure;
13
+ * - at least one example at that version refused: `warning`, with each
14
+ * refusal ID, its site count, the examples that carry it and its
15
+ * `live/LIMITATIONS.md` link;
16
+ * - every example clear: quiet.
17
+ *
18
+ * Every message names the tier. The catalog holds the static tier only, and
19
+ * no figure here counts a gauntlet run (choudoufu#2292 ruling: two tiers,
20
+ * never mixed).
21
+ *
22
+ * A stock root (`mode: "state"`) is skipped: the table measures choudoufu.
23
+ * A range is TF039's finding and a missing `version` TF004's, so both are
24
+ * skipped here. A `//subdir` source is looked up by its module repository.
25
+ *
26
+ * Off by default: `report-only` in the audit catalog, as TF039 is.
27
+ * terragucci turns it on and holds the product wording (terragucci#1286).
28
+ *
29
+ * Scope: root and child modules of a live root alike.
30
+ */
31
+
32
+ import type {
33
+ PostSynthCheck,
34
+ PostSynthContext,
35
+ PostSynthDiagnostic,
36
+ } from "@intentius/chant/lint/post-synth";
37
+ import { isResourceDeclarable } from "@intentius/chant/declarable";
38
+ import { MODULE_TYPE, type BlockBody } from "../../hcl/parse";
39
+ import { classifyModuleSource, isExactVersionConstraint } from "./module-source";
40
+ import {
41
+ MODULE_CATALOG_SOURCE,
42
+ MODULE_COMPATIBILITY_PAGE,
43
+ moduleCompatibility,
44
+ type ModuleCompatibility,
45
+ } from "../../module-catalog";
46
+
47
+ const tierNote = (): string =>
48
+ `static tier of choudoufu's module table, measured ${MODULE_CATALOG_SOURCE.date} with hashicorp/aws ${MODULE_CATALOG_SOURCE.awsProvider}`;
49
+
50
+ /** The TF047 message for one module call, or undefined when the call is clear. */
51
+ export function catalogMessage(address: string, c: ModuleCompatibility): string | undefined {
52
+ const pin = `${c.module} ${c.version}`;
53
+ switch (c.status) {
54
+ case "clear":
55
+ return undefined;
56
+ case "unknown-module":
57
+ return (
58
+ `Module "${address}" pins ${pin}, and the ${tierNote()} has no row for this module. ` +
59
+ `Run \`choudoufu live-check\` on the root to see whether it plans. The table: ${MODULE_COMPATIBILITY_PAGE}`
60
+ );
61
+ case "unknown-version":
62
+ return (
63
+ `Module "${address}" pins ${pin}, and the ${tierNote()} has no row at this version. ` +
64
+ `It measured ${c.measuredVersions.join(", ")}. ` +
65
+ `Run \`choudoufu live-check\` on the root, or pin a measured version. The table: ${MODULE_COMPATIBILITY_PAGE}`
66
+ );
67
+ case "refused": {
68
+ const ids = c.refusals
69
+ .map((r) => `${r.id} (${r.sites} site${r.sites === 1 ? "" : "s"} in ${r.examples.join(", ")}; ${r.docsUrl})`)
70
+ .join("; ");
71
+ return (
72
+ `Module "${address}" pins ${pin}. In the ${tierNote()}, ${c.refusedExamples} of ${c.examples} ` +
73
+ `examples at this version are refused: ${ids}. ` +
74
+ "A call that uses the same features is refused the same way; `choudoufu live-check` names each site in this root."
75
+ );
76
+ }
77
+ }
78
+ }
79
+
80
+ export const tf047: PostSynthCheck = {
81
+ id: "TF047",
82
+ description: "Live root pins a module version the known-module catalog has no row for, or one with refusals",
83
+
84
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
85
+ const diagnostics: PostSynthDiagnostic[] = [];
86
+
87
+ for (const [key, entity] of ctx.entities) {
88
+ if (entity.entityType !== MODULE_TYPE || !isResourceDeclarable(entity)) continue;
89
+ const props = entity.props as { address?: unknown; body?: unknown; mode?: unknown };
90
+ if (props.mode !== "live") continue;
91
+ const body = (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
92
+ const source = typeof body.source === "string" ? body.source : undefined;
93
+ if (source === undefined || classifyModuleSource(source).kind !== "registry") continue;
94
+ const version = body.version;
95
+ if (typeof version !== "string" || !isExactVersionConstraint(version)) continue;
96
+
97
+ const compat = moduleCompatibility(source, version);
98
+ if (compat === undefined) continue;
99
+ const address = typeof props.address === "string" ? props.address : key;
100
+ const message = catalogMessage(address, compat);
101
+ if (message === undefined) continue;
102
+
103
+ diagnostics.push({
104
+ checkId: "TF047",
105
+ severity: compat.status === "refused" ? "warning" : "info",
106
+ message,
107
+ entity: key,
108
+ lexicon: "terraform",
109
+ });
110
+ }
111
+
112
+ return diagnostics;
113
+ },
114
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * choudoufu's live-plan omission codes, in chant's vocabulary.
3
+ *
4
+ * Its own module so the observation reader (`./describe-resources.ts`) and
5
+ * the drift adapters (`./drift-record.ts`) read one table, and the drift
6
+ * adapters bundle without the reader's process and file-system imports.
7
+ */
8
+
9
+ import type { UnobservedReason } from "@intentius/chant/observation";
10
+
11
+ /**
12
+ * choudoufu's omission reason codes, mapped onto chant's total vocabulary
13
+ * (`internal/live/projection/result.go`). `"absent"` is not an
14
+ * {@link UnobservedReason}: it is the tri-state's OBSERVED-ABSENT, the one
15
+ * omission that is a claim of knowledge rather than of ignorance.
16
+ *
17
+ * A code this table does not know maps to `read-failed`, which is the safe
18
+ * direction: no NOT-OBSERVED verdict ever becomes a create or a delete.
19
+ */
20
+ export const LIVE_PLAN_OMISSION_REASONS: Readonly<Record<string, UnobservedReason | "absent">> = {
21
+ // The provider was asked and reported, normally, that nothing is there.
22
+ ABSENT: "absent",
23
+ // The provider errored, or could not be reached in a usable order.
24
+ FAILED: "read-failed",
25
+ PARENT_UNAVAILABLE: "read-failed",
26
+ CYCLE: "read-failed",
27
+ INCOMPLETE_BLOCK: "read-failed",
28
+ LISTED_NOT_IMPORTABLE: "read-failed",
29
+ // Something exists at the identity and does not carry this estate's marker.
30
+ // Normally answered by the `unowned[]` section before this table is read;
31
+ // this entry is what happens if a document ever carries the omission alone.
32
+ UNOWNED: "read-failed",
33
+ // The mode cannot name, list or tag this instance at all: a server-assigned
34
+ // identity with no marker to find it by, a resource with no cloud object to
35
+ // read, or an address another declared instance has taken over.
36
+ NEEDS_DISCOVERY: "unsupported-kind",
37
+ UNREADABLE: "unsupported-kind",
38
+ SUPERSEDED: "unsupported-kind",
39
+ // The run's own tag index saw an object at the identity and the import
40
+ // read could not confirm it (choudoufu #1043). The read failed; the
41
+ // object is not proposed for creation.
42
+ VERIFIED_NOT_IMPORTABLE: "read-failed",
43
+ // -target or -exclude left the block out of the plan graph (choudoufu
44
+ // #1176): nothing was read because the caller asked for it not to be.
45
+ OUT_OF_SCOPE: "filtered",
46
+ };
47
+
48
+ /** Does this text name a credentials or authorization failure rather than any other error? */
49
+ export function readsAsCredentials(text: string): boolean {
50
+ return /\b(no valid credential|credentials?|not authorized|unauthorized|accessdenied|access denied|expiredtoken|invalidclienttokenid|signature)\b/i.test(
51
+ text,
52
+ );
53
+ }
54
+
@@ -0,0 +1,70 @@
1
+ /**
2
+ * #4552: a choudoufu root's rows keep terragucci's format (`planRows` in
3
+ * terragucci's apply-rows.ts), and Terraform, tofu and Terragrunt keep the
4
+ * target grain and refuse the resource grain by name.
5
+ */
6
+ import { describe, expect, test } from "vitest";
7
+ import type { ChangeSetPart } from "@intentius/chant/change-set";
8
+ import { terraformDefaultLockGrain, terraformLockGrainRefusal, terraformLockRows, terraformLockRowsFromPart } from "./lock-rows";
9
+
10
+ const plan = {
11
+ resource_changes: [
12
+ { address: "aws_s3_bucket.logs", mode: "managed", change: { actions: ["update"], before: { id: "logs-1" } } },
13
+ { address: "aws_sqs_queue.jobs", mode: "managed", change: { actions: ["create"], before: null } },
14
+ { address: "aws_iam_role.app", previous_address: "aws_iam_role.old", mode: "managed", change: { actions: ["no-op"], before: { id: "app" } } },
15
+ { address: "aws_iam_role.svc", previous_address: "aws_iam_role.legacy", mode: "managed", change: { actions: ["delete", "create"], before: { id: "svc" } } },
16
+ { address: "data.aws_caller_identity.me", mode: "data", change: { actions: ["read"] } },
17
+ ],
18
+ };
19
+
20
+ describe("terraformLockRows", () => {
21
+ test("estate rows, the previous address of a moved instance, and object rows for anything but a create", () => {
22
+ expect(terraformLockRows("net", "e01", plan).map((r) => r.key)).toEqual([
23
+ "estate e01 aws_iam_role.legacy",
24
+ "estate e01 aws_iam_role.svc",
25
+ "estate e01 aws_s3_bucket.logs",
26
+ "estate e01 aws_sqs_queue.jobs",
27
+ "object logs-1",
28
+ "object svc",
29
+ ]);
30
+ });
31
+
32
+ test("a root with no estate is keyed by its path", () => {
33
+ expect(terraformLockRows("live/net", undefined, plan).map((r) => r.key)).toEqual([
34
+ "object logs-1",
35
+ "object svc",
36
+ "root live/net aws_iam_role.legacy",
37
+ "root live/net aws_iam_role.svc",
38
+ "root live/net aws_s3_bucket.logs",
39
+ "root live/net aws_sqs_queue.jobs",
40
+ ]);
41
+ });
42
+
43
+ test("from a change-set part: the same keys less the previous address", () => {
44
+ const part: ChangeSetPart = {
45
+ member: { member: "net", lexicon: "terraform", planner: "choudoufu", scope: "e01", status: "planned", planDigest: "sha256:x", holes: [] },
46
+ entries: [
47
+ { member: "net", lexicon: "terraform", planner: "choudoufu", address: "aws_s3_bucket.logs", type: "aws_s3_bucket", id: "logs-1", action: "update", attributes: [] },
48
+ { member: "net", lexicon: "terraform", planner: "choudoufu", address: "aws_sqs_queue.jobs", type: "aws_sqs_queue", action: "no-op", attributes: [] },
49
+ ],
50
+ };
51
+ expect(terraformLockRowsFromPart("net", part).map((r) => r.key)).toEqual(["estate e01 aws_s3_bucket.logs", "object logs-1"]);
52
+ });
53
+ });
54
+
55
+ describe("the grain", () => {
56
+ test("resource under choudoufu, the root under Terraform and tofu", () => {
57
+ expect(terraformDefaultLockGrain("choudoufu")).toBe("resource");
58
+ expect(terraformDefaultLockGrain("terraform")).toBe("target");
59
+ expect(terraformDefaultLockGrain("tofu")).toBe("target");
60
+ });
61
+
62
+ test("resource is refused by engine name off choudoufu; target and repo are not", () => {
63
+ expect(terraformLockGrainRefusal("resource", "choudoufu")).toBeUndefined();
64
+ expect(terraformLockGrainRefusal("resource", "terraform")).toMatch(/^Terraform keeps/);
65
+ expect(terraformLockGrainRefusal("resource", "tofu")).toMatch(/^tofu keeps/);
66
+ expect(terraformLockGrainRefusal("resource", "terraform", "/usr/local/bin/terragrunt")).toMatch(/^Terragrunt keeps/);
67
+ expect(terraformLockGrainRefusal("target", "terraform")).toBeUndefined();
68
+ expect(terraformLockGrainRefusal("repo", "tofu")).toBeUndefined();
69
+ });
70
+ });