@intentius/chant-lexicon-terraform 0.57.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/dist/composites/terraform-adopt-op.d.ts +126 -0
  2. package/dist/composites/terraform-adopt-op.d.ts.map +1 -0
  3. package/dist/composites/terraform-apply-op.d.ts +63 -16
  4. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  5. package/dist/composites/terraform-watch-op.d.ts +80 -36
  6. package/dist/composites/terraform-watch-op.d.ts.map +1 -1
  7. package/dist/config.d.ts +49 -2
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/describe-resources.d.ts +256 -16
  10. package/dist/describe-resources.d.ts.map +1 -1
  11. package/dist/hcl/descend.d.ts +98 -0
  12. package/dist/hcl/descend.d.ts.map +1 -0
  13. package/dist/hcl/marker.d.ts +130 -0
  14. package/dist/hcl/marker.d.ts.map +1 -0
  15. package/dist/hcl/parse.d.ts +142 -9
  16. package/dist/hcl/parse.d.ts.map +1 -1
  17. package/dist/hcl/references.d.ts +76 -0
  18. package/dist/hcl/references.d.ts.map +1 -0
  19. package/dist/hcl/roots.d.ts +22 -3
  20. package/dist/hcl/roots.d.ts.map +1 -1
  21. package/dist/hcl/suppressions.d.ts +54 -0
  22. package/dist/hcl/suppressions.d.ts.map +1 -0
  23. package/dist/hcl/value.d.ts +77 -0
  24. package/dist/hcl/value.d.ts.map +1 -0
  25. package/dist/index.d.ts +5 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/integrity.json +32 -5
  28. package/dist/lint/audit-catalog.d.ts +3 -2
  29. package/dist/lint/audit-catalog.d.ts.map +1 -1
  30. package/dist/lint/audit-lineage.d.ts +68 -0
  31. package/dist/lint/audit-lineage.d.ts.map +1 -1
  32. package/dist/lint/post-synth/blocks.d.ts +53 -0
  33. package/dist/lint/post-synth/blocks.d.ts.map +1 -0
  34. package/dist/lint/post-synth/fixtures/load.d.ts +37 -0
  35. package/dist/lint/post-synth/fixtures/load.d.ts.map +1 -0
  36. package/dist/lint/post-synth/index.d.ts.map +1 -1
  37. package/dist/lint/post-synth/module-source.d.ts +38 -0
  38. package/dist/lint/post-synth/module-source.d.ts.map +1 -0
  39. package/dist/lint/post-synth/scope.d.ts +65 -0
  40. package/dist/lint/post-synth/scope.d.ts.map +1 -0
  41. package/dist/lint/post-synth/tf001.d.ts +18 -0
  42. package/dist/lint/post-synth/tf001.d.ts.map +1 -1
  43. package/dist/lint/post-synth/tf002.d.ts +32 -0
  44. package/dist/lint/post-synth/tf002.d.ts.map +1 -0
  45. package/dist/lint/post-synth/tf003.d.ts +22 -0
  46. package/dist/lint/post-synth/tf003.d.ts.map +1 -0
  47. package/dist/lint/post-synth/tf004.d.ts +22 -0
  48. package/dist/lint/post-synth/tf004.d.ts.map +1 -0
  49. package/dist/lint/post-synth/tf005.d.ts +28 -0
  50. package/dist/lint/post-synth/tf005.d.ts.map +1 -0
  51. package/dist/lint/post-synth/tf006.d.ts +21 -0
  52. package/dist/lint/post-synth/tf006.d.ts.map +1 -0
  53. package/dist/lint/post-synth/tf007.d.ts +21 -0
  54. package/dist/lint/post-synth/tf007.d.ts.map +1 -0
  55. package/dist/lint/post-synth/tf008.d.ts +22 -0
  56. package/dist/lint/post-synth/tf008.d.ts.map +1 -0
  57. package/dist/lint/post-synth/tf009.d.ts +24 -0
  58. package/dist/lint/post-synth/tf009.d.ts.map +1 -0
  59. package/dist/lint/post-synth/tf010.d.ts +19 -0
  60. package/dist/lint/post-synth/tf010.d.ts.map +1 -0
  61. package/dist/lint/post-synth/tf011.d.ts +18 -0
  62. package/dist/lint/post-synth/tf011.d.ts.map +1 -0
  63. package/dist/lint/post-synth/tf012.d.ts +17 -0
  64. package/dist/lint/post-synth/tf012.d.ts.map +1 -0
  65. package/dist/lint/post-synth/tf013.d.ts +21 -0
  66. package/dist/lint/post-synth/tf013.d.ts.map +1 -0
  67. package/dist/lint/post-synth/tf014.d.ts +25 -0
  68. package/dist/lint/post-synth/tf014.d.ts.map +1 -0
  69. package/dist/lint/post-synth/tf015.d.ts +21 -0
  70. package/dist/lint/post-synth/tf015.d.ts.map +1 -0
  71. package/dist/lint/post-synth/tf016.d.ts +30 -0
  72. package/dist/lint/post-synth/tf016.d.ts.map +1 -0
  73. package/dist/lint/post-synth/tf017.d.ts +19 -0
  74. package/dist/lint/post-synth/tf017.d.ts.map +1 -0
  75. package/dist/lint/post-synth/tf018.d.ts +20 -0
  76. package/dist/lint/post-synth/tf018.d.ts.map +1 -0
  77. package/dist/lint/post-synth/tf019.d.ts +25 -0
  78. package/dist/lint/post-synth/tf019.d.ts.map +1 -0
  79. package/dist/lint/post-synth/tf020.d.ts +38 -0
  80. package/dist/lint/post-synth/tf020.d.ts.map +1 -0
  81. package/dist/lint/post-synth/tf021.d.ts +41 -0
  82. package/dist/lint/post-synth/tf021.d.ts.map +1 -0
  83. package/dist/lint/post-synth/tf022.d.ts +24 -0
  84. package/dist/lint/post-synth/tf022.d.ts.map +1 -0
  85. package/dist/lint/post-synth/tf024.d.ts +24 -0
  86. package/dist/lint/post-synth/tf024.d.ts.map +1 -0
  87. package/dist/lint/post-synth/tf025.d.ts +29 -0
  88. package/dist/lint/post-synth/tf025.d.ts.map +1 -0
  89. package/dist/lint/post-synth/tf026.d.ts +21 -0
  90. package/dist/lint/post-synth/tf026.d.ts.map +1 -0
  91. package/dist/lint/rules/fixtures/load.d.ts +13 -0
  92. package/dist/lint/rules/fixtures/load.d.ts.map +1 -0
  93. package/dist/lint/rules/plan-before-apply.d.ts +11 -0
  94. package/dist/lint/rules/plan-before-apply.d.ts.map +1 -1
  95. package/dist/lint/secret-shape.d.ts +68 -0
  96. package/dist/lint/secret-shape.d.ts.map +1 -0
  97. package/dist/live-ownership.d.ts +55 -0
  98. package/dist/live-ownership.d.ts.map +1 -0
  99. package/dist/lsp/option-keys.d.ts.map +1 -1
  100. package/dist/manifest.json +1 -1
  101. package/dist/okf/index.md +24 -0
  102. package/dist/okf/rules/TF002.md +11 -0
  103. package/dist/okf/rules/TF003.md +11 -0
  104. package/dist/okf/rules/TF004.md +11 -0
  105. package/dist/okf/rules/TF005.md +11 -0
  106. package/dist/okf/rules/TF006.md +11 -0
  107. package/dist/okf/rules/TF007.md +11 -0
  108. package/dist/okf/rules/TF008.md +11 -0
  109. package/dist/okf/rules/TF009.md +11 -0
  110. package/dist/okf/rules/TF010.md +11 -0
  111. package/dist/okf/rules/TF011.md +11 -0
  112. package/dist/okf/rules/TF012.md +11 -0
  113. package/dist/okf/rules/TF013.md +11 -0
  114. package/dist/okf/rules/TF014.md +11 -0
  115. package/dist/okf/rules/TF015.md +11 -0
  116. package/dist/okf/rules/TF016.md +11 -0
  117. package/dist/okf/rules/TF017.md +11 -0
  118. package/dist/okf/rules/TF018.md +11 -0
  119. package/dist/okf/rules/TF019.md +11 -0
  120. package/dist/okf/rules/TF020.md +11 -0
  121. package/dist/okf/rules/TF021.md +11 -0
  122. package/dist/okf/rules/TF022.md +11 -0
  123. package/dist/okf/rules/TF024.md +11 -0
  124. package/dist/okf/rules/TF025.md +11 -0
  125. package/dist/okf/rules/TF026.md +11 -0
  126. package/dist/op/activities/index.d.ts +13 -6
  127. package/dist/op/activities/index.d.ts.map +1 -1
  128. package/dist/op/activities/live-detect.d.ts +51 -0
  129. package/dist/op/activities/live-detect.d.ts.map +1 -0
  130. package/dist/op/activities/terraform.d.ts +445 -17
  131. package/dist/op/activities/terraform.d.ts.map +1 -1
  132. package/dist/op/activity-contracts.d.ts +148 -0
  133. package/dist/op/activity-contracts.d.ts.map +1 -0
  134. package/dist/op/adoption.d.ts +118 -0
  135. package/dist/op/adoption.d.ts.map +1 -0
  136. package/dist/op/builders.d.ts +46 -6
  137. package/dist/op/builders.d.ts.map +1 -1
  138. package/dist/op/resolve-root-mode.d.ts +45 -0
  139. package/dist/op/resolve-root-mode.d.ts.map +1 -0
  140. package/dist/plugin.d.ts.map +1 -1
  141. package/dist/rules/blocks.ts +109 -0
  142. package/dist/rules/module-source.ts +121 -0
  143. package/dist/rules/plan-before-apply.ts +11 -0
  144. package/dist/rules/scope.ts +107 -0
  145. package/dist/rules/tf001.ts +32 -8
  146. package/dist/rules/tf002.ts +151 -0
  147. package/dist/rules/tf003.ts +71 -0
  148. package/dist/rules/tf004.ts +63 -0
  149. package/dist/rules/tf005.ts +76 -0
  150. package/dist/rules/tf006.ts +53 -0
  151. package/dist/rules/tf007.ts +78 -0
  152. package/dist/rules/tf008.ts +75 -0
  153. package/dist/rules/tf009.ts +59 -0
  154. package/dist/rules/tf010.ts +51 -0
  155. package/dist/rules/tf011.ts +49 -0
  156. package/dist/rules/tf012.ts +47 -0
  157. package/dist/rules/tf013.ts +63 -0
  158. package/dist/rules/tf014.ts +72 -0
  159. package/dist/rules/tf015.ts +63 -0
  160. package/dist/rules/tf016.ts +75 -0
  161. package/dist/rules/tf017.ts +50 -0
  162. package/dist/rules/tf018.ts +72 -0
  163. package/dist/rules/tf019.ts +72 -0
  164. package/dist/rules/tf020.ts +123 -0
  165. package/dist/rules/tf021.ts +126 -0
  166. package/dist/rules/tf022.ts +61 -0
  167. package/dist/rules/tf024.ts +70 -0
  168. package/dist/rules/tf025.ts +101 -0
  169. package/dist/rules/tf026.ts +80 -0
  170. package/dist/skills/chant-terraform.md +1 -1
  171. package/package.json +7 -2
  172. package/src/__fixtures__/live/main.tf +28 -0
  173. package/src/__fixtures__/live-adopt/estate.chdf.hcl +6 -0
  174. package/src/__fixtures__/live-adopt/main.tf +21 -0
  175. package/src/__fixtures__/live-estate/README.md +58 -0
  176. package/src/__fixtures__/live-estate/compute.tf +10 -0
  177. package/src/__fixtures__/live-estate/estate.chdf.hcl +1 -0
  178. package/src/__fixtures__/live-estate/logs.tf +8 -0
  179. package/src/__fixtures__/live-estate/network.tf +24 -0
  180. package/src/__fixtures__/live-estate/rule.tf +11 -0
  181. package/src/__fixtures__/live-estate/unowned.tf +21 -0
  182. package/src/__fixtures__/live-estate/versions.tf +31 -0
  183. package/src/__fixtures__/live-ls.json +98 -0
  184. package/src/__fixtures__/live-plan.json +285 -0
  185. package/src/__fixtures__/live-sidecar/estate.chdf.hcl +3 -0
  186. package/src/__fixtures__/live-sidecar/main.tf +24 -0
  187. package/src/__fixtures__/module-tree/README.md +16 -0
  188. package/src/__fixtures__/module-tree/cycle/main.tf +3 -0
  189. package/src/__fixtures__/module-tree/cycle/modules/a/main.tf +9 -0
  190. package/src/__fixtures__/module-tree/cycle/modules/b/main.tf +9 -0
  191. package/src/__fixtures__/module-tree/missing/main.tf +9 -0
  192. package/src/__fixtures__/module-tree/outside/root/main.tf +9 -0
  193. package/src/__fixtures__/module-tree/root/main.tf +28 -0
  194. package/src/__fixtures__/module-tree/root/modules/cdn/main.tf +15 -0
  195. package/src/__fixtures__/module-tree/root/modules/cdn/modules/bucket/main.tf +11 -0
  196. package/src/__fixtures__/module-tree/shared/main.tf +5 -0
  197. package/src/__fixtures__/references/main.tf +56 -0
  198. package/src/codegen/lint-rules-page.test.ts +28 -0
  199. package/src/composites/terraform-adopt-op.acceptance.test.ts +166 -0
  200. package/src/composites/terraform-adopt-op.test.ts +170 -0
  201. package/src/composites/terraform-adopt-op.ts +197 -0
  202. package/src/composites/terraform-apply-op.acceptance.test.ts +183 -5
  203. package/src/composites/terraform-apply-op.test.ts +167 -15
  204. package/src/composites/terraform-apply-op.ts +105 -28
  205. package/src/composites/terraform-watch-op.test.ts +85 -26
  206. package/src/composites/terraform-watch-op.ts +130 -73
  207. package/src/config.ts +32 -3
  208. package/src/describe-resources.live.test.ts +598 -0
  209. package/src/describe-resources.test.ts +53 -0
  210. package/src/describe-resources.ts +811 -28
  211. package/src/hcl/descend.test.ts +162 -0
  212. package/src/hcl/descend.ts +273 -0
  213. package/src/hcl/marker.test.ts +147 -0
  214. package/src/hcl/marker.ts +235 -0
  215. package/src/hcl/parse.test.ts +69 -0
  216. package/src/hcl/parse.ts +249 -27
  217. package/src/hcl/references.test.ts +116 -0
  218. package/src/hcl/references.ts +172 -0
  219. package/src/hcl/roots.ts +59 -5
  220. package/src/hcl/suppressions.test.ts +147 -0
  221. package/src/hcl/suppressions.ts +204 -0
  222. package/src/hcl/value.test.ts +73 -0
  223. package/src/hcl/value.ts +106 -0
  224. package/src/index.ts +31 -1
  225. package/src/lint/audit-catalog.ts +264 -3
  226. package/src/lint/audit-lineage.ts +319 -1
  227. package/src/lint/audit.test.ts +40 -1
  228. package/src/lint/post-synth/blocks.ts +109 -0
  229. package/src/lint/post-synth/fixtures/TF001/negative.tf +28 -0
  230. package/src/lint/post-synth/fixtures/TF001/positive.tf +24 -0
  231. package/src/lint/post-synth/fixtures/TF002/negative.tf +17 -0
  232. package/src/lint/post-synth/fixtures/TF002/positive-legacy.tf +10 -0
  233. package/src/lint/post-synth/fixtures/TF002/positive-no-version.tf +12 -0
  234. package/src/lint/post-synth/fixtures/TF002/positive.tf +7 -0
  235. package/src/lint/post-synth/fixtures/TF003/negative.tf +10 -0
  236. package/src/lint/post-synth/fixtures/TF003/positive.tf +8 -0
  237. package/src/lint/post-synth/fixtures/TF004/negative-git.tf +3 -0
  238. package/src/lint/post-synth/fixtures/TF004/negative-local.tf +3 -0
  239. package/src/lint/post-synth/fixtures/TF004/negative.tf +4 -0
  240. package/src/lint/post-synth/fixtures/TF004/positive.tf +3 -0
  241. package/src/lint/post-synth/fixtures/TF005/negative-local.tf +3 -0
  242. package/src/lint/post-synth/fixtures/TF005/negative-registry.tf +3 -0
  243. package/src/lint/post-synth/fixtures/TF005/negative-sha.tf +3 -0
  244. package/src/lint/post-synth/fixtures/TF005/negative.tf +3 -0
  245. package/src/lint/post-synth/fixtures/TF005/positive-branch.tf +3 -0
  246. package/src/lint/post-synth/fixtures/TF005/positive-non-semver.tf +3 -0
  247. package/src/lint/post-synth/fixtures/TF005/positive.tf +3 -0
  248. package/src/lint/post-synth/fixtures/TF006/negative.tf +5 -0
  249. package/src/lint/post-synth/fixtures/TF006/positive-empty-default.tf +11 -0
  250. package/src/lint/post-synth/fixtures/TF006/positive.tf +6 -0
  251. package/src/lint/post-synth/fixtures/TF007/negative.tf +10 -0
  252. package/src/lint/post-synth/fixtures/TF007/positive.tf +9 -0
  253. package/src/lint/post-synth/fixtures/TF008/negative-variable.tf +10 -0
  254. package/src/lint/post-synth/fixtures/TF008/negative.tf +8 -0
  255. package/src/lint/post-synth/fixtures/TF008/positive-other-provider.tf +10 -0
  256. package/src/lint/post-synth/fixtures/TF008/positive.tf +5 -0
  257. package/src/lint/post-synth/fixtures/TF009/negative.tf +10 -0
  258. package/src/lint/post-synth/fixtures/TF009/positive.tf +4 -0
  259. package/src/lint/post-synth/fixtures/TF010/negative.tf +5 -0
  260. package/src/lint/post-synth/fixtures/TF010/positive.tf +4 -0
  261. package/src/lint/post-synth/fixtures/TF011/negative.tf +5 -0
  262. package/src/lint/post-synth/fixtures/TF011/positive-blank.tf +5 -0
  263. package/src/lint/post-synth/fixtures/TF011/positive.tf +4 -0
  264. package/src/lint/post-synth/fixtures/TF012/negative.tf +4 -0
  265. package/src/lint/post-synth/fixtures/TF012/positive.tf +3 -0
  266. package/src/lint/post-synth/fixtures/TF013/negative.tf +8 -0
  267. package/src/lint/post-synth/fixtures/TF013/positive-data.tf +8 -0
  268. package/src/lint/post-synth/fixtures/TF013/positive.tf +8 -0
  269. package/src/lint/post-synth/fixtures/TF014/negative/main.tf +26 -0
  270. package/src/lint/post-synth/fixtures/TF014/negative/modules/cdn/main.tf +19 -0
  271. package/src/lint/post-synth/fixtures/TF014/positive/main.tf +16 -0
  272. package/src/lint/post-synth/fixtures/TF014/positive/modules/cdn/main.tf +15 -0
  273. package/src/lint/post-synth/fixtures/TF015/negative/main.tf +13 -0
  274. package/src/lint/post-synth/fixtures/TF015/negative/modules/cdn/main.tf +16 -0
  275. package/src/lint/post-synth/fixtures/TF015/positive/main.tf +13 -0
  276. package/src/lint/post-synth/fixtures/TF015/positive/modules/cdn/main.tf +14 -0
  277. package/src/lint/post-synth/fixtures/TF016/negative-template.tf +7 -0
  278. package/src/lint/post-synth/fixtures/TF016/negative.tf +16 -0
  279. package/src/lint/post-synth/fixtures/TF016/positive-shared-file.tf +12 -0
  280. package/src/lint/post-synth/fixtures/TF016/positive.tf +13 -0
  281. package/src/lint/post-synth/fixtures/TF017/negative.tf +4 -0
  282. package/src/lint/post-synth/fixtures/TF017/positive.tf +5 -0
  283. package/src/lint/post-synth/fixtures/TF018/negative.tf +9 -0
  284. package/src/lint/post-synth/fixtures/TF018/positive-data.tf +9 -0
  285. package/src/lint/post-synth/fixtures/TF018/positive.tf +4 -0
  286. package/src/lint/post-synth/fixtures/TF019/negative.tf +14 -0
  287. package/src/lint/post-synth/fixtures/TF019/positive-output.tf +5 -0
  288. package/src/lint/post-synth/fixtures/TF019/positive.tf +16 -0
  289. package/src/lint/post-synth/fixtures/TF020/negative.tf +43 -0
  290. package/src/lint/post-synth/fixtures/TF020/positive.tf +32 -0
  291. package/src/lint/post-synth/fixtures/TF020/scoped/main.tf +21 -0
  292. package/src/lint/post-synth/fixtures/TF020/scoped/modules/cdn/main.tf +13 -0
  293. package/src/lint/post-synth/fixtures/TF021/negative.tf +31 -0
  294. package/src/lint/post-synth/fixtures/TF021/positive-length.tf +8 -0
  295. package/src/lint/post-synth/fixtures/TF021/positive.tf +8 -0
  296. package/src/lint/post-synth/fixtures/TF022/negative-locator.tf +7 -0
  297. package/src/lint/post-synth/fixtures/TF022/negative.tf +10 -0
  298. package/src/lint/post-synth/fixtures/TF022/positive-nested.tf +10 -0
  299. package/src/lint/post-synth/fixtures/TF022/positive.tf +6 -0
  300. package/src/lint/post-synth/fixtures/TF023/negative/main.tf +15 -0
  301. package/src/lint/post-synth/fixtures/TF023/positive/main.tf +9 -0
  302. package/src/lint/post-synth/fixtures/TF023/positive/state.json +24 -0
  303. package/src/lint/post-synth/fixtures/TF024/live-clean/main.tf +9 -0
  304. package/src/lint/post-synth/fixtures/TF024/live-with-backend/main.tf +14 -0
  305. package/src/lint/post-synth/fixtures/TF024/live-with-cloud/main.tf +16 -0
  306. package/src/lint/post-synth/fixtures/TF025/live-clean/main.tf +13 -0
  307. package/src/lint/post-synth/fixtures/TF025/live-with-workspace-ref/main.tf +13 -0
  308. package/src/lint/post-synth/fixtures/TF026/live-default/main.tf +9 -0
  309. package/src/lint/post-synth/fixtures/TF026/live-explicit-delete/main.tf +13 -0
  310. package/src/lint/post-synth/fixtures/TF026/live-keep/main.tf +13 -0
  311. package/src/lint/post-synth/fixtures/load.ts +108 -0
  312. package/src/lint/post-synth/index.ts +48 -0
  313. package/src/lint/post-synth/module-rules.test.ts +153 -0
  314. package/src/lint/post-synth/module-source.test.ts +75 -0
  315. package/src/lint/post-synth/module-source.ts +121 -0
  316. package/src/lint/post-synth/post-synth.test.ts +14 -15
  317. package/src/lint/post-synth/scope.ts +107 -0
  318. package/src/lint/post-synth/single-block.test.ts +263 -0
  319. package/src/lint/post-synth/suppression.test.ts +110 -0
  320. package/src/lint/post-synth/tf001.ts +32 -8
  321. package/src/lint/post-synth/tf002.test.ts +93 -0
  322. package/src/lint/post-synth/tf002.ts +151 -0
  323. package/src/lint/post-synth/tf003.test.ts +75 -0
  324. package/src/lint/post-synth/tf003.ts +71 -0
  325. package/src/lint/post-synth/tf004.test.ts +47 -0
  326. package/src/lint/post-synth/tf004.ts +63 -0
  327. package/src/lint/post-synth/tf005.test.ts +57 -0
  328. package/src/lint/post-synth/tf005.ts +76 -0
  329. package/src/lint/post-synth/tf006.ts +53 -0
  330. package/src/lint/post-synth/tf007.ts +78 -0
  331. package/src/lint/post-synth/tf008.ts +75 -0
  332. package/src/lint/post-synth/tf009.ts +59 -0
  333. package/src/lint/post-synth/tf010.ts +51 -0
  334. package/src/lint/post-synth/tf011.ts +49 -0
  335. package/src/lint/post-synth/tf012.ts +47 -0
  336. package/src/lint/post-synth/tf013.ts +63 -0
  337. package/src/lint/post-synth/tf014.ts +72 -0
  338. package/src/lint/post-synth/tf015.ts +63 -0
  339. package/src/lint/post-synth/tf016.ts +75 -0
  340. package/src/lint/post-synth/tf017.ts +50 -0
  341. package/src/lint/post-synth/tf018.ts +72 -0
  342. package/src/lint/post-synth/tf019.ts +72 -0
  343. package/src/lint/post-synth/tf020.ts +123 -0
  344. package/src/lint/post-synth/tf021.ts +126 -0
  345. package/src/lint/post-synth/tf022.ts +61 -0
  346. package/src/lint/post-synth/tf024.test.ts +42 -0
  347. package/src/lint/post-synth/tf024.ts +70 -0
  348. package/src/lint/post-synth/tf025.test.ts +46 -0
  349. package/src/lint/post-synth/tf025.ts +101 -0
  350. package/src/lint/post-synth/tf026.test.ts +56 -0
  351. package/src/lint/post-synth/tf026.ts +80 -0
  352. package/src/lint/presets.test.ts +72 -0
  353. package/src/lint/rules/fixtures/TF101/negative-op.ts +2 -0
  354. package/src/lint/rules/fixtures/TF101/positive-op.ts +2 -0
  355. package/src/lint/rules/fixtures/load.ts +53 -0
  356. package/src/lint/rules/plan-before-apply.test.ts +112 -7
  357. package/src/lint/rules/plan-before-apply.ts +11 -0
  358. package/src/lint/secret-shape.test.ts +150 -0
  359. package/src/lint/secret-shape.ts +159 -0
  360. package/src/live-ownership.ts +60 -0
  361. package/src/lsp/completions.test.ts +3 -2
  362. package/src/lsp/option-keys.ts +10 -5
  363. package/src/op/activities/choudoufu.acceptance.test.ts +143 -0
  364. package/src/op/activities/choudoufu.test.ts +693 -0
  365. package/src/op/activities/index.ts +40 -4
  366. package/src/op/activities/live-detect.test.ts +143 -0
  367. package/src/op/activities/live-detect.ts +162 -0
  368. package/src/op/activities/registry.test.ts +11 -3
  369. package/src/op/activities/terraform.test.ts +7 -3
  370. package/src/op/activities/terraform.ts +823 -69
  371. package/src/op/activity-contracts.test.ts +147 -0
  372. package/src/op/activity-contracts.ts +178 -0
  373. package/src/op/adoption.test.ts +170 -0
  374. package/src/op/adoption.ts +241 -0
  375. package/src/op/builders.test.ts +60 -5
  376. package/src/op/builders.ts +93 -5
  377. package/src/op/resolve-root-mode.test.ts +85 -0
  378. package/src/op/resolve-root-mode.ts +72 -0
  379. package/src/plugin.test.ts +68 -0
  380. package/src/plugin.ts +75 -9
  381. package/src/skills/chant-terraform.md +1 -1
@@ -0,0 +1,147 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { isActivityContract, validateActivitySteps, validateStepOutputRefs, stepOutput, type ActivityContract } from "@intentius/chant/op";
3
+ import * as contracts from "./activity-contracts";
4
+ import * as activities from "./activities";
5
+
6
+ const CONTRACTS: Map<string, ActivityContract> = new Map(
7
+ Object.values(contracts).filter(isActivityContract).map((c) => [c.name, c]),
8
+ );
9
+
10
+ /**
11
+ * The activities this lexicon contributes to the run-time registry — every
12
+ * exported function that is one, as `collectActivities` keys them.
13
+ * `terraformInitCommand` and friends are pure builders the tests assert on,
14
+ * not activities a step can name, so the list is spelled out rather than
15
+ * derived from the module's exports.
16
+ */
17
+ const ACTIVITY_NAMES = [
18
+ "terraformInit",
19
+ "terraformPlan",
20
+ "terraformApply",
21
+ "terraformShow",
22
+ "choudoufuLivePlan",
23
+ "choudoufuLiveLs",
24
+ "choudoufuLiveCheck",
25
+ "choudoufuAdopt",
26
+ ];
27
+
28
+ describe("terraform activity contracts (#2101)", () => {
29
+ test("every activity this lexicon ships has a contract", () => {
30
+ for (const name of ACTIVITY_NAMES) {
31
+ expect(CONTRACTS.get(name), `no contract declared for ${name}`).toBeDefined();
32
+ }
33
+ });
34
+
35
+ test("each name in ACTIVITY_NAMES really is an exported activity", () => {
36
+ for (const name of ACTIVITY_NAMES) {
37
+ expect(typeof (activities as unknown as Record<string, unknown>)[name]).toBe("function");
38
+ }
39
+ });
40
+
41
+ test("the module exports nothing but contracts", () => {
42
+ for (const [key, value] of Object.entries(contracts)) {
43
+ expect(isActivityContract(value), `${key} is not an ActivityContract`).toBe(true);
44
+ }
45
+ });
46
+
47
+ test("every contract declares a return schema — OPS013 needs one to validate a reference", () => {
48
+ for (const name of ACTIVITY_NAMES) {
49
+ expect(CONTRACTS.get(name)?.returns, `${name} declares no return schema`).toBeDefined();
50
+ }
51
+ });
52
+
53
+ test("`root` is declared as the entity every step touches (#2022)", () => {
54
+ for (const name of ACTIVITY_NAMES) {
55
+ expect(CONTRACTS.get(name)?.entities).toEqual(["root"]);
56
+ }
57
+ });
58
+
59
+ // ── The failure classes the contracts exist to catch ───────────────────
60
+
61
+ const op = (steps: unknown[]) => ({
62
+ name: "app-apply",
63
+ phases: [{ name: "Phase", steps: steps as never[] }],
64
+ });
65
+
66
+ test("a misspelled arg key is an error, not a silently dropped field", () => {
67
+ const issues = validateActivitySteps(
68
+ op([{ kind: "activity", fn: "terraformPlan", args: { root: "app", planfile: "chant.tfplan" } }]),
69
+ CONTRACTS,
70
+ );
71
+ expect(issues.some((i) => i.message.includes("planfile"))).toBe(true);
72
+ });
73
+
74
+ test("a missing `root` is an error", () => {
75
+ const issues = validateActivitySteps(
76
+ op([{ kind: "activity", fn: "terraformApply", args: {} }]),
77
+ CONTRACTS,
78
+ );
79
+ expect(issues.length).toBeGreaterThan(0);
80
+ });
81
+
82
+ test("the args the examples' composites build validate clean", () => {
83
+ const issues = validateActivitySteps(
84
+ op([
85
+ { kind: "activity", fn: "terraformInit", args: { root: "app" }, profile: "longInfra" },
86
+ { kind: "activity", fn: "terraformPlan", args: { root: "app", planFile: "chant.tfplan" }, id: "plan", profile: "longInfra" },
87
+ { kind: "activity", fn: "terraformShow", args: { root: "app", planFile: stepOutput("plan", "planFile") }, profile: "fastIdempotent" },
88
+ { kind: "activity", fn: "terraformApply", args: { root: "app", planFile: stepOutput("plan", "planFile") }, profile: "longInfra" },
89
+ ]),
90
+ CONTRACTS,
91
+ );
92
+ expect(issues).toEqual([]);
93
+ });
94
+
95
+ test("`plan.out.planFile` resolves against terraformPlan's declared return type", () => {
96
+ const issues = validateStepOutputRefs(
97
+ op([
98
+ { kind: "activity", fn: "terraformPlan", args: { root: "app" }, id: "plan" },
99
+ { kind: "activity", fn: "terraformApply", args: { root: "app", planFile: stepOutput("plan", "planFile") } },
100
+ ]),
101
+ CONTRACTS,
102
+ );
103
+ expect(issues).toEqual([]);
104
+ });
105
+
106
+ test("`plan.out.text` — the field a finding mode posts — resolves too", () => {
107
+ const issues = validateStepOutputRefs(
108
+ op([
109
+ { kind: "activity", fn: "terraformPlan", args: { root: "app" }, id: "plan" },
110
+ { kind: "activity", fn: "reconcilePr", args: { env: "app", body: stepOutput("plan", "text") } },
111
+ ]),
112
+ CONTRACTS,
113
+ );
114
+ expect(issues).toEqual([]);
115
+ });
116
+
117
+ test("a live watch's three outcome attributes exist on choudoufuLivePlan's return type", () => {
118
+ const issues = validateActivitySteps(
119
+ op([
120
+ {
121
+ kind: "activity",
122
+ fn: "choudoufuLivePlan",
123
+ args: { root: "app" },
124
+ id: "plan",
125
+ outcomeAttribute: [
126
+ { name: "Drift", from: "drift" },
127
+ { name: "Unowned", from: "unowned" },
128
+ { name: "Adoptable", from: "adoptable" },
129
+ ],
130
+ },
131
+ ]),
132
+ CONTRACTS,
133
+ );
134
+ expect(issues).toEqual([]);
135
+ });
136
+
137
+ test("a reference into terraform's own `-json` document does not resolve, by design", () => {
138
+ const issues = validateStepOutputRefs(
139
+ op([
140
+ { kind: "activity", fn: "terraformPlan", args: { root: "app" }, id: "plan" },
141
+ { kind: "activity", fn: "reconcilePr", args: { env: "app", body: stepOutput("plan", "json.resource_changes") } },
142
+ ]),
143
+ CONTRACTS,
144
+ );
145
+ expect(issues.some((i) => i.message.includes("does not exist"))).toBe(true);
146
+ });
147
+ });
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Activity contracts for this lexicon's own `op/activities` (chant #2101).
3
+ *
4
+ * The schema-shaped sibling of `./activities/index.ts`: that module is
5
+ * resolved by convention at `@intentius/chant-lexicon-terraform/op/activities`
6
+ * for the run-time registry, and this one at
7
+ * `@intentius/chant-lexicon-terraform/op/activity-contracts` for build-time
8
+ * validation. `loadActivityContracts`
9
+ * (`packages/core/src/op/activity-contract-registry.ts`) merges what it finds
10
+ * here into the map core's own OPS012 and OPS013 validate every `Chant::Op`
11
+ * step against. Since #2122 moved those checks into core they fire on every
12
+ * project that declares an Op, so an Op built out of terraform steps is
13
+ * checked wherever it is built. Before this existed, both examples that carry
14
+ * an Op had to keep it outside `src/` where `chant dev check-lexicon`'s
15
+ * example build could not see it.
16
+ *
17
+ * Each contract's `args` schema mirrors the corresponding `*Args` interface in
18
+ * `./activities/terraform.ts`, and each `returns` schema the corresponding
19
+ * `*Result`. Authored with `z.strictObject(...)`, never `z.object(...)`: the
20
+ * default form silently drops an unrecognized key instead of rejecting it, so
21
+ * a misspelled `planfile` would vanish rather than fail the build. The
22
+ * `returns` schemas are the half OPS013 needs — `plan.out.planFile` feeding an
23
+ * Apply step, `plan.out.text` feeding a `reconcilePr` body — so a field a step
24
+ * references has to be spelled here for the reference to validate.
25
+ *
26
+ * `json` is `z.unknown()` throughout: it is terraform's own `-json` document,
27
+ * whose shape is the provider's, not chant's. A dot-path reference into it
28
+ * therefore does not resolve, which is deliberate. Reference `text`, or one of
29
+ * the counts projected out of the document, instead of reaching into
30
+ * terraform's JSON from an Op.
31
+ */
32
+
33
+ import { z } from "zod";
34
+ import { activityContract } from "@intentius/chant/op";
35
+
36
+ /** `root` and `cwd`, the two fields every activity in this lexicon takes ({@link TerraformRootArgs}). */
37
+ const rootArgs = { root: z.string(), cwd: z.string().optional() };
38
+
39
+ /**
40
+ * `adds`/`changes`/`destroys`, the counts projected out of a plan's
41
+ * `resource_changes` ({@link PlanChangeCounts}).
42
+ */
43
+ const planChangeCounts = { adds: z.number(), changes: z.number(), destroys: z.number() };
44
+
45
+ /**
46
+ * One adoption candidate as `../adoption.ts` models it. Loose on purpose: an
47
+ * Op never reaches into a candidate's fields, it hands the whole array from a
48
+ * live-plan step to `choudoufuAdopt`, and a whole-value reference is validated
49
+ * without walking the shape.
50
+ */
51
+ const adoptionCandidate = z.object({}).loose();
52
+
53
+ /**
54
+ * The `root` arg names the terraform root module the step acts on — the
55
+ * estate-side thing every activity here touches — so op.json's IR resolves it
56
+ * into the step's `entities` (#2022) and a renderer can join the step to the
57
+ * root instead of guessing off `cwd`, which is only where the config search
58
+ * starts.
59
+ */
60
+ const rootEntity = { entities: ["root"] };
61
+
62
+ export const terraformInitContract = activityContract(
63
+ "terraformInit",
64
+ z.strictObject({
65
+ ...rootArgs,
66
+ upgrade: z.boolean().optional(),
67
+ reconfigure: z.boolean().optional(),
68
+ }),
69
+ z.object({ dir: z.string(), workspace: z.string().optional() }),
70
+ rootEntity,
71
+ );
72
+
73
+ export const terraformPlanContract = activityContract(
74
+ "terraformPlan",
75
+ z.strictObject({
76
+ ...rootArgs,
77
+ planFile: z.string().optional(),
78
+ destroy: z.boolean().optional(),
79
+ }),
80
+ z.object({
81
+ ...planChangeCounts,
82
+ changed: z.boolean(),
83
+ planFile: z.string(),
84
+ dir: z.string(),
85
+ json: z.unknown(),
86
+ text: z.string(),
87
+ }),
88
+ rootEntity,
89
+ );
90
+
91
+ export const terraformApplyContract = activityContract(
92
+ "terraformApply",
93
+ z.strictObject({ ...rootArgs, planFile: z.string().optional() }),
94
+ z.object({
95
+ planFile: z.string().optional(),
96
+ dir: z.string(),
97
+ applied: z.boolean(),
98
+ refused: z.enum(["approval-mismatch", "wrong-estate"]).optional(),
99
+ refusal: z.string().optional(),
100
+ }),
101
+ rootEntity,
102
+ );
103
+
104
+ export const terraformShowContract = activityContract(
105
+ "terraformShow",
106
+ z.strictObject({ ...rootArgs, planFile: z.string().optional() }),
107
+ z.object({
108
+ ...planChangeCounts,
109
+ source: z.enum(["plan", "state"]),
110
+ json: z.unknown(),
111
+ text: z.string(),
112
+ dir: z.string(),
113
+ planFile: z.string().optional(),
114
+ }),
115
+ rootEntity,
116
+ );
117
+
118
+ export const choudoufuLivePlanContract = activityContract(
119
+ "choudoufuLivePlan",
120
+ z.strictObject({
121
+ ...rootArgs,
122
+ estate: z.string().optional(),
123
+ adoptionOnly: z.boolean().optional(),
124
+ }),
125
+ z.object({
126
+ ...planChangeCounts,
127
+ unowned: z.number(),
128
+ adoptable: z.number(),
129
+ drift: z.boolean(),
130
+ json: z.unknown(),
131
+ text: z.string(),
132
+ ledger: z.string(),
133
+ finding: z.string(),
134
+ adoptions: z.array(adoptionCandidate),
135
+ contested: z.array(adoptionCandidate),
136
+ ambiguous: z.number(),
137
+ dir: z.string(),
138
+ documentPath: z.string(),
139
+ estate: z.string(),
140
+ }),
141
+ rootEntity,
142
+ );
143
+
144
+ export const choudoufuLiveLsContract = activityContract(
145
+ "choudoufuLiveLs",
146
+ z.strictObject({
147
+ ...rootArgs,
148
+ estate: z.string().optional(),
149
+ consistent: z.boolean().optional(),
150
+ }),
151
+ z.object({ json: z.unknown(), dir: z.string(), estate: z.string() }),
152
+ rootEntity,
153
+ );
154
+
155
+ export const choudoufuLiveCheckContract = activityContract(
156
+ "choudoufuLiveCheck",
157
+ z.strictObject({ ...rootArgs }),
158
+ z.object({ refused: z.boolean(), json: z.unknown(), text: z.string(), dir: z.string() }),
159
+ rootEntity,
160
+ );
161
+
162
+ export const choudoufuAdoptContract = activityContract(
163
+ "choudoufuAdopt",
164
+ z.strictObject({
165
+ ...rootArgs,
166
+ adoptions: z.array(adoptionCandidate),
167
+ contested: z.array(adoptionCandidate).optional(),
168
+ }),
169
+ z.object({
170
+ mechanism: z.literal("tag-write"),
171
+ adopted: z.array(z.string()),
172
+ adoptedCount: z.number(),
173
+ refused: z.array(z.object({ addr: z.string(), identity: z.string(), reason: z.string() })),
174
+ ambiguous: z.number(),
175
+ dir: z.string(),
176
+ }),
177
+ rootEntity,
178
+ );
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The adoption ledger (#2105): reading `live-plan -json`'s `unowned` section,
3
+ * pairing it with the commands `-adoption-only` printed, and rendering it.
4
+ *
5
+ * The document fragments below are the shape choudoufu actually emits, taken
6
+ * from `views.StatelessUnowned`'s json tags (`internal/command/live_plan.go`)
7
+ * and checked against the recorded document #2104 captured off the emulator:
8
+ * an adoptable row carries `adopt_tofu_estate`/`adopt_tofu_address`, and a row
9
+ * held by somebody else carries `tofu_estate` and neither of the other two.
10
+ */
11
+
12
+ import { describe, test, expect } from "vitest";
13
+ import { parseAdoptionCommands, readAdoptionLedger, renderAdoptionLedger } from "./adoption";
14
+
15
+ const adoptable = (addr: string, identity: string, type = "aws_vpc") => ({
16
+ addr,
17
+ type,
18
+ identity,
19
+ adopt_tofu_estate: "prod-networking",
20
+ adopt_tofu_address: addr,
21
+ });
22
+
23
+ describe("readAdoptionLedger (#2105)", () => {
24
+ test("a row carrying both marker values is adoptable", () => {
25
+ const ledger = readAdoptionLedger({ unowned: [adoptable("aws_vpc.main", "vpc-0abc")] });
26
+ expect(ledger.adoptions).toEqual([
27
+ {
28
+ addr: "aws_vpc.main",
29
+ type: "aws_vpc",
30
+ identity: "vpc-0abc",
31
+ markerEstate: "prod-networking",
32
+ markerAddress: "aws_vpc.main",
33
+ },
34
+ ]);
35
+ expect(ledger.contested).toEqual([]);
36
+ expect(ledger.ambiguous).toBe(0);
37
+ });
38
+
39
+ test("a row held by another estate is neither adoptable nor contested", () => {
40
+ // Adoption was not this run's to offer, so there is no tag write to refuse.
41
+ const ledger = readAdoptionLedger({
42
+ unowned: [
43
+ {
44
+ addr: "aws_cloudwatch_log_group.held_elsewhere",
45
+ type: "aws_cloudwatch_log_group",
46
+ identity: "/estate/held-elsewhere",
47
+ tofu_estate: "other-estate",
48
+ },
49
+ ],
50
+ });
51
+ expect(ledger.adoptions).toEqual([]);
52
+ expect(ledger.contested).toEqual([]);
53
+ });
54
+
55
+ test("two live resources at one declared address are contested, never adoptable", () => {
56
+ const ledger = readAdoptionLedger({
57
+ unowned: [
58
+ adoptable("aws_vpc.main", "vpc-0abc"),
59
+ adoptable("aws_vpc.main", "vpc-0def"),
60
+ adoptable("aws_subnet.app", "subnet-01", "aws_subnet"),
61
+ ],
62
+ });
63
+ expect(ledger.adoptions.map((c) => c.addr)).toEqual(["aws_subnet.app"]);
64
+ expect(ledger.contested.map((c) => c.identity)).toEqual(["vpc-0abc", "vpc-0def"]);
65
+ // One address is contested, and both of its candidates are listed, so the
66
+ // report shows what the choice is between.
67
+ expect(ledger.ambiguous).toBe(1);
68
+ });
69
+
70
+ test("a command from the -adoption-only render rides on the matching candidate", () => {
71
+ const commands = new Map([["aws_vpc.main", "aws ec2 create-tags --resources 'vpc-0abc'"]]);
72
+ const ledger = readAdoptionLedger(
73
+ { unowned: [adoptable("aws_vpc.main", "vpc-0abc"), adoptable("aws_subnet.app", "subnet-01", "aws_subnet")] },
74
+ commands,
75
+ );
76
+ expect(ledger.adoptions.find((c) => c.addr === "aws_vpc.main")?.command).toBe(
77
+ "aws ec2 create-tags --resources 'vpc-0abc'",
78
+ );
79
+ expect(ledger.adoptions.find((c) => c.addr === "aws_subnet.app")?.command).toBeUndefined();
80
+ });
81
+
82
+ test("a document with no unowned section, or none at all, is an empty ledger", () => {
83
+ expect(readAdoptionLedger({ bound: [] })).toEqual({ adoptions: [], contested: [], ambiguous: 0 });
84
+ expect(readAdoptionLedger(undefined)).toEqual({ adoptions: [], contested: [], ambiguous: 0 });
85
+ expect(readAdoptionLedger({ unowned: "not a list" })).toEqual({ adoptions: [], contested: [], ambiguous: 0 });
86
+ });
87
+ });
88
+
89
+ describe("parseAdoptionCommands (#2105)", () => {
90
+ // The row shape `StatelessAdoptionHuman.adoptionSection` prints: the address
91
+ // line, then indented detail lines.
92
+ const RENDER = [
93
+ "",
94
+ "Adoptable now: 2 resource instances",
95
+ "",
96
+ "Each of these is a live resource this run found at a declared resource's identity.",
97
+ "",
98
+ " aws_vpc.main <- aws_vpc vpc-0abc",
99
+ " matched on: cidr_block=10.0.0.0/16",
100
+ " adopt with: aws ec2 create-tags --resources 'vpc-0abc' --tags 'Key=tofu-estate,Value=prod'",
101
+ " or write: tofu-estate=prod tofu-address=aws_vpc.main",
102
+ " aws_iam_role.app <- aws_iam_role app-role",
103
+ " or write: tofu-estate=prod tofu-address=aws_iam_role.app",
104
+ "",
105
+ ].join("\n");
106
+
107
+ test("pairs each address with the command printed under it", () => {
108
+ expect(parseAdoptionCommands(RENDER).get("aws_vpc.main")).toBe(
109
+ "aws ec2 create-tags --resources 'vpc-0abc' --tags 'Key=tofu-estate,Value=prod'",
110
+ );
111
+ });
112
+
113
+ test("an address with no printed command is absent, not empty", () => {
114
+ // IAM has its own tagging call, which choudoufu does not spell out.
115
+ const commands = parseAdoptionCommands(RENDER);
116
+ expect(commands.has("aws_iam_role.app")).toBe(false);
117
+ expect(commands.size).toBe(1);
118
+ });
119
+
120
+ test("a render with no adoptable section yields nothing", () => {
121
+ expect(parseAdoptionCommands("Adoption: 3 declared resource instances\n\nNo changes.\n").size).toBe(0);
122
+ });
123
+ });
124
+
125
+ describe("renderAdoptionLedger (#2105)", () => {
126
+ test("one line per adoptable match, carrying the address, identity and both marker values", () => {
127
+ const text = renderAdoptionLedger(
128
+ readAdoptionLedger({ unowned: [adoptable("aws_vpc.main", "vpc-0abc")] }),
129
+ "prod-networking",
130
+ );
131
+ expect(text).toContain(
132
+ " aws_vpc.main <- aws_vpc vpc-0abc write: tofu-estate=prod-networking tofu-address=aws_vpc.main",
133
+ );
134
+ expect(text).toContain('Adoptable now: 1 live resource, estate "prod-networking"');
135
+ });
136
+
137
+ test("an empty ledger still says so, rather than rendering nothing", () => {
138
+ const text = renderAdoptionLedger(readAdoptionLedger({ unowned: [] }), "prod-networking");
139
+ expect(text).toContain("Adoptable now: nothing");
140
+ });
141
+
142
+ test("contested addresses are listed under their own heading with no marker values offered", () => {
143
+ const text = renderAdoptionLedger(
144
+ readAdoptionLedger({ unowned: [adoptable("aws_vpc.main", "vpc-0abc"), adoptable("aws_vpc.main", "vpc-0def")] }),
145
+ "prod-networking",
146
+ );
147
+ expect(text).toContain("Ambiguous: 1 declared address with more than one candidate");
148
+ expect(text).toContain(" aws_vpc.main <- aws_vpc vpc-0abc");
149
+ expect(text).toContain(" aws_vpc.main <- aws_vpc vpc-0def");
150
+ // Nothing in the contested block offers a write: there is no single one.
151
+ const contestedBlock = text.slice(text.indexOf("Ambiguous:"));
152
+ expect(contestedBlock).not.toContain("write: tofu-estate=");
153
+ });
154
+
155
+ test("nothing outside the unowned section reaches the rendering", () => {
156
+ // The document carries every declared instance the plan bound and every
157
+ // one it could not read, with identities and reasons. The ledger is a
158
+ // projection of `unowned` alone, and this is what says so.
159
+ const document = {
160
+ unowned: [adoptable("aws_vpc.main", "vpc-0abc")],
161
+ bound: [{ addr: "aws_db_instance.prod", identity: "prod-db-0f1e2d3c" }],
162
+ omissions: [{ addr: "aws_kms_key.signing", detail: "provider error" }],
163
+ };
164
+ const text = renderAdoptionLedger(readAdoptionLedger(document), "prod-networking");
165
+ expect(text).not.toContain("aws_db_instance.prod");
166
+ expect(text).not.toContain("prod-db-0f1e2d3c");
167
+ expect(text).not.toContain("aws_kms_key.signing");
168
+ expect(text).not.toContain("adopt_tofu_estate");
169
+ });
170
+ });