@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,166 @@
1
+ /**
2
+ * The acceptance test is choudoufu, not a fixture (#2105): adopt a live
3
+ * resource nothing has marked, end to end.
4
+ *
5
+ * `terraform-adopt-op.test.ts` next door proves the shape chant emits.
6
+ * `choudoufu.test.ts` proves the activities' contract against a stubbed child
7
+ * process. This proves the shape does the thing: create an unmarked VPC
8
+ * directly against choudoufu's pinned emulator, so it is a live resource this
9
+ * estate does not own at an identity this root's configuration declares; run
10
+ * the Ledger step and expect exactly one adoptable match with the two marker
11
+ * values on it; run the Adopt step and let it write them; re-plan and expect
12
+ * the estate to own the same VPC, with nothing left adoptable.
13
+ *
14
+ * The Op's phases are not run through `runOpLocally` here, deliberately.
15
+ * `TerraformAdoptOp` always emits a gate — adoption moves the estate's
16
+ * boundary, and there is no ungated form — so a local run would stop at the
17
+ * gate and record a pending fact rather than reach the Adopt phase
18
+ * (`packages/core/src/op/gate.ts`). So this drives the two activities the Op's
19
+ * Ledger and Adopt phases are built from, in the same order and with the same
20
+ * hand-off, which is the part a real binary can falsify. `choudoufu.acceptance.test.ts` takes the same approach for the same
21
+ * reason.
22
+ *
23
+ * ## Why this skips today
24
+ *
25
+ * choudoufu#894: `live-plan -json` is reachable only through the `-estate`
26
+ * form, and that form is refused on a configuration that names its own estate
27
+ * ("Estate named by both the live block and -estate"), while the same root
28
+ * without `-estate` refuses with "Machine-readable output is not available
29
+ * under live resource markers yet". The one configuration shape choudoufu's
30
+ * own docs lead with is the one shape the #788 document cannot be produced
31
+ * for, so `choudoufuLivePlan` throws on any real live root until that lands.
32
+ * Found by chant #2104 against a source build at HEAD.
33
+ *
34
+ * The gate below names it, alongside the ordinary two: no binary, and no
35
+ * emulator. When #894 ships, drop `CHOUDOUFU_894_OPEN` and this suite runs as
36
+ * written.
37
+ *
38
+ * Gating copied from `./terraform-apply-op.acceptance.test.ts` (`onPath`),
39
+ * which in turn copies `lexicons/k3s/src/serializer.acceptance.test.ts`'s
40
+ * pattern of skipping with the reason named in the describe title rather than
41
+ * failing when the real dependency is absent.
42
+ */
43
+
44
+ import { execSync } from "node:child_process";
45
+ import { cpSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
46
+ import { tmpdir } from "node:os";
47
+ import { join } from "node:path";
48
+ import { afterAll, describe, expect, it } from "vitest";
49
+ import { choudoufuAdopt, choudoufuLivePlan, terraformInit } from "../op/activities/terraform";
50
+
51
+ function onPath(cmd: string): boolean {
52
+ try {
53
+ execSync(`command -v ${cmd}`, { stdio: "ignore" });
54
+ return true;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * https://github.com/INTENTIUS/choudoufu/issues/894. Flip to `false` when the
62
+ * `-json` document becomes reachable on a root that declares its own estate.
63
+ * chant #2168 tracks this and the other four reversals that unblock together.
64
+ */
65
+ const CHOUDOUFU_894_OPEN = true;
66
+
67
+ const emulatorEndpoint = process.env.CHOUDOUFU_EMULATOR_ENDPOINT;
68
+
69
+ const skipReason = !onPath("choudoufu")
70
+ ? "no choudoufu binary on PATH"
71
+ : !onPath("aws")
72
+ ? "no aws CLI on PATH (the unmarked resource is created with it, and adopted through it)"
73
+ : !emulatorEndpoint
74
+ ? "CHOUDOUFU_EMULATOR_ENDPOINT is not set (bring up choudoufu's `just smoke` emulator stack and export it)"
75
+ : CHOUDOUFU_894_OPEN
76
+ ? "choudoufu#894: live-plan -json is refused on a configuration that declares its own estate, " +
77
+ "so there is no adoption ledger to act on yet"
78
+ : "";
79
+
80
+ const FIXTURE = join(import.meta.dirname, "..", "__fixtures__", "live-adopt");
81
+ const ESTATE = "chant-adopt-fixture";
82
+ const CIDR = "10.77.0.0/16";
83
+ const workspaces: string[] = [];
84
+
85
+ afterAll(() => {
86
+ for (const dir of workspaces) rmSync(dir, { recursive: true, force: true });
87
+ });
88
+
89
+ /** A fresh copy of the live-adopt fixture, wired up with a real project config. */
90
+ function project(): string {
91
+ const dir = mkdtempSync(join(tmpdir(), "chant-adopt-accept-"));
92
+ workspaces.push(dir);
93
+ cpSync(FIXTURE, join(dir, "root"), { recursive: true });
94
+ writeFileSync(
95
+ join(dir, "chant.config.json"),
96
+ JSON.stringify(
97
+ { lexicons: ["terraform"], terraform: { binary: "choudoufu", roots: { estate: { dir: "./root" } } } },
98
+ null,
99
+ 2,
100
+ ),
101
+ );
102
+ return dir;
103
+ }
104
+
105
+ describe.skipIf(skipReason !== "")(
106
+ `TerraformAdoptOp adopts an unmarked live resource${skipReason ? ` (skipped: ${skipReason})` : ""}`,
107
+ () => {
108
+ it(
109
+ "ledgers one adoptable VPC, writes its two markers, and re-plans with it owned",
110
+ { timeout: 600_000 },
111
+ async () => {
112
+ const dir = project();
113
+
114
+ process.env.AWS_ENDPOINT_URL ??= emulatorEndpoint;
115
+ process.env.AWS_ACCESS_KEY_ID ??= "choudoufu-emulator";
116
+ process.env.AWS_SECRET_ACCESS_KEY ??= "choudoufu-emulator";
117
+ process.env.AWS_DEFAULT_REGION ??= "us-east-1";
118
+
119
+ // The stock-created resource: a VPC at the cidr this root declares,
120
+ // carrying no ownership marker at all. Nothing about it says which
121
+ // configuration made it, which is exactly the migration situation.
122
+ execSync(
123
+ `aws ec2 create-vpc --cidr-block ${CIDR} --endpoint-url ${emulatorEndpoint} --region us-east-1`,
124
+ { stdio: "pipe" },
125
+ );
126
+
127
+ await terraformInit({ root: "estate", cwd: dir });
128
+
129
+ const ledger = await choudoufuLivePlan({ root: "estate", cwd: dir, adoptionOnly: true });
130
+ expect(ledger.estate).toBe(ESTATE);
131
+ expect(ledger.contested).toEqual([]);
132
+ expect(ledger.adoptions).toHaveLength(1);
133
+ const [candidate] = ledger.adoptions;
134
+ expect(candidate.addr).toBe("aws_vpc.adoptable");
135
+ expect(candidate.markerEstate).toBe(ESTATE);
136
+ expect(candidate.markerAddress).toBe("aws_vpc.adoptable");
137
+ // A VPC is tagged through `ec2 create-tags`, which choudoufu prints a
138
+ // command for; a candidate with none would be refused below instead.
139
+ expect(candidate.command).toContain("create-tags");
140
+ expect(ledger.ledger).toContain("aws_vpc.adoptable <- aws_vpc vpc-");
141
+
142
+ const adopted = await choudoufuAdopt({
143
+ root: "estate",
144
+ cwd: dir,
145
+ adoptions: ledger.adoptions,
146
+ contested: ledger.contested,
147
+ });
148
+ expect(adopted.mechanism).toBe("tag-write");
149
+ expect(adopted.adopted).toEqual(["aws_vpc.adoptable"]);
150
+ expect(adopted.refused).toEqual([]);
151
+
152
+ // The markers are the whole ownership answer, so a re-plan binds the
153
+ // same live VPC instead of proposing a second one: nothing is left
154
+ // unowned at this estate's declared identity, and nothing adoptable.
155
+ const after = await choudoufuLivePlan({ root: "estate", cwd: dir });
156
+ expect(after.adoptable).toBe(0);
157
+ expect(after.unowned).toBe(0);
158
+ expect(after.adoptions).toEqual([]);
159
+
160
+ // And the estate now says so: the VPC is bound at the declared address.
161
+ const bound = (after.json as { bound?: Array<{ addr?: string; identity?: string }> }).bound ?? [];
162
+ expect(bound.map((b) => b.addr)).toContain("aws_vpc.adoptable");
163
+ },
164
+ );
165
+ },
166
+ );
@@ -0,0 +1,170 @@
1
+ /**
2
+ * TerraformAdoptOp composite tests (#2105): the four phases, the gate that is
3
+ * always there, the two references the Adopt step takes off the ledger, and
4
+ * the rule an adoption Op exists to keep — an ambiguous match is reported and
5
+ * never written.
6
+ */
7
+
8
+ import { describe, test, expect } from "vitest";
9
+ import {
10
+ collectStepOutputRefs,
11
+ isStepOutputRef,
12
+ type ActivityStep,
13
+ type GateStep,
14
+ type OpConfig,
15
+ type StepDefinition,
16
+ } from "@intentius/chant/op";
17
+
18
+ import { TerraformAdoptOp } from "./terraform-adopt-op";
19
+
20
+ function props(config: Parameters<typeof TerraformAdoptOp>[0]): OpConfig {
21
+ return (TerraformAdoptOp(config).op as unknown as { props: OpConfig }).props;
22
+ }
23
+
24
+ const phaseNames = (op: OpConfig): string[] => op.phases.map((p) => p.name);
25
+ const isActivity = (s: StepDefinition): s is ActivityStep => s.kind === "activity";
26
+ const steps = (op: OpConfig): ActivityStep[] => op.phases.flatMap((p) => p.steps.filter(isActivity));
27
+ const base = { name: "estate-adopt", root: "estate" };
28
+
29
+ describe("TerraformAdoptOp phases (#2105)", () => {
30
+ test("Check, Ledger, Gate, Adopt, in that order", () => {
31
+ const op = props(base);
32
+ expect(phaseNames(op)).toEqual(["Check", "Ledger", "Gate", "Adopt"]);
33
+ expect(steps(op).map((s) => s.fn)).toEqual(["choudoufuLiveCheck", "choudoufuLivePlan", "choudoufuAdopt"]);
34
+ });
35
+
36
+ test("Check runs live-check first, and reports whether the root was refused", () => {
37
+ const check = props(base).phases[0].steps[0] as ActivityStep;
38
+ expect(check.fn).toBe("choudoufuLiveCheck");
39
+ expect(check.outcomeAttribute).toEqual({ name: "Refused", from: "refused" });
40
+ // No cloud calls, so this is the cheapest place to learn the root cannot
41
+ // move under markers at all.
42
+ expect(check.profile).toBe("fastIdempotent");
43
+ });
44
+
45
+ test("Ledger is the -adoption-only live plan, and publishes both counts", () => {
46
+ const ledger = props(base).phases[1].steps[0] as ActivityStep;
47
+ expect(ledger.fn).toBe("choudoufuLivePlan");
48
+ expect(ledger.id).toBe("ledger");
49
+ expect(ledger.args?.adoptionOnly).toBe(true);
50
+ expect(ledger.outcomeAttribute).toEqual([
51
+ { name: "Adoptable", from: "adoptable" },
52
+ { name: "Ambiguous", from: "ambiguous" },
53
+ ]);
54
+ });
55
+
56
+ test("every step names the configured root", () => {
57
+ for (const step of steps(props(base))) expect(step.args?.root).toBe("estate");
58
+ });
59
+
60
+ test("cwd rides every step, and no cwd means no cwd arg", () => {
61
+ for (const step of steps(props({ ...base, cwd: "/repo" }))) expect(step.args?.cwd).toBe("/repo");
62
+ for (const step of steps(props(base))) expect(step.args && "cwd" in step.args).toBe(false);
63
+ });
64
+
65
+ test("an explicit estate rides the Ledger step only", () => {
66
+ const op = props({ ...base, estate: "prod-networking" });
67
+ expect((op.phases[1].steps[0] as ActivityStep).args?.estate).toBe("prod-networking");
68
+ expect((op.phases[0].steps[0] as ActivityStep).args?.estate).toBeUndefined();
69
+ });
70
+ });
71
+
72
+ describe("TerraformAdoptOp gates on the ledger (#2105)", () => {
73
+ const gateOf = (op: OpConfig): GateStep => {
74
+ const step = op.phases[2].steps[0];
75
+ expect(step.kind).toBe("gate");
76
+ return step as GateStep;
77
+ };
78
+
79
+ test("the gate is always emitted: there is no ungated adoption", () => {
80
+ // Unlike TerraformApplyOp there is no `gate: "never"` here. Adoption moves
81
+ // the estate's boundary onto resources it did not own, which is not a
82
+ // thing to do unattended.
83
+ expect(gateOf(props(base)).signalName).toBe("approve-estate-adopt");
84
+ expect(Object.keys(TerraformAdoptOp(base))).toEqual(["op"]);
85
+ });
86
+
87
+ test("the description names the ledger as the approval subject", () => {
88
+ const description = gateOf(props(base)).description!;
89
+ expect(description).toContain("adoption ledger");
90
+ expect(description).toContain("tofu-estate and tofu-address");
91
+ expect(description).toContain("an ambiguous address is never adopted");
92
+ });
93
+
94
+ test("signal name, timeout and description are overridable", () => {
95
+ const gate = gateOf(props({ ...base, signalName: "ok", gateTimeout: "2h", gateDescription: "mine" }));
96
+ expect(gate).toMatchObject({ signalName: "ok", timeout: "2h", description: "mine" });
97
+ });
98
+
99
+ test("no timeout given means the gate carries none, so core's own default applies", () => {
100
+ expect(gateOf(props(base)).timeout).toBeUndefined();
101
+ });
102
+ });
103
+
104
+ describe("TerraformAdoptOp adopts the ledger's matches and nothing else (#2105)", () => {
105
+ const adoptStep = (op: OpConfig): ActivityStep => op.phases[3].steps[0] as ActivityStep;
106
+
107
+ test("the Adopt step takes the ledger's adoptable set by reference", () => {
108
+ const adopt = adoptStep(props(base));
109
+ expect(adopt.fn).toBe("choudoufuAdopt");
110
+ expect(isStepOutputRef(adopt.args?.adoptions)).toBe(true);
111
+ expect(adopt.args?.adoptions).toMatchObject({ step: "ledger", path: "adoptions" });
112
+ });
113
+
114
+ test("an ambiguous match is handed over as `contested`, which is a separate field from `adoptions`", () => {
115
+ // `readAdoptionLedger` never puts a contested candidate in `adoptions`, so
116
+ // the Adopt step cannot write one however it behaves; `contested` is
117
+ // passed so the Op's own result names what it declined and why.
118
+ const adopt = adoptStep(props(base));
119
+ expect(adopt.args?.contested).toMatchObject({ step: "ledger", path: "contested" });
120
+ expect(adopt.args?.contested).not.toEqual(adopt.args?.adoptions);
121
+ });
122
+
123
+ test("the Adopt step reports how many resources it claimed", () => {
124
+ expect(adoptStep(props(base)).outcomeAttribute).toEqual({ name: "Adopted", from: "adoptedCount" });
125
+ });
126
+
127
+ test("the Op references exactly the two ledger fields and no document", () => {
128
+ const op = props(base);
129
+ const refs = op.phases.flatMap((p) => p.steps.filter(isActivity)).flatMap((s) => collectStepOutputRefs(s.args));
130
+ expect(refs.map((r) => r.path).sort()).toEqual(["adoptions", "contested"]);
131
+ expect(refs.every((r) => r.step === "ledger")).toBe(true);
132
+ // The bound/omissions/unowned document never leaves the Ledger step.
133
+ expect(JSON.stringify(op)).not.toContain('"json"');
134
+ });
135
+ });
136
+
137
+ describe("TerraformAdoptOp compensation (#2105)", () => {
138
+ test("compensate: true throws, because un-adopting is untag", () => {
139
+ expect(() => TerraformAdoptOp({ ...base, compensate: true })).toThrow(/un-adopting is untag/);
140
+ });
141
+
142
+ test("an object with no command throws the same way", () => {
143
+ expect(() => TerraformAdoptOp({ ...base, compensate: {} })).toThrow(/TerraformAdoptOp "estate-adopt"/);
144
+ });
145
+
146
+ test("a supplied command becomes an onFailure Rollback phase", () => {
147
+ const op = props({ ...base, compensate: { command: "./untag.sh" } });
148
+ expect(op.onFailure?.map((p) => p.name)).toEqual(["Rollback"]);
149
+ expect((op.onFailure![0].steps[0] as ActivityStep).args?.cmd).toBe("./untag.sh");
150
+ });
151
+
152
+ test("compensate: false and omitting it both leave no onFailure", () => {
153
+ expect(props(base).onFailure).toBeUndefined();
154
+ expect(props({ ...base, compensate: false }).onFailure).toBeUndefined();
155
+ });
156
+ });
157
+
158
+ describe("TerraformAdoptOp labels (#2105, #2118)", () => {
159
+ test("marks the Op as an adoption on a live root", () => {
160
+ expect(props(base).labels).toEqual({
161
+ Adopt: "true",
162
+ TerraformRoot: "estate",
163
+ TerraformMode: "live",
164
+ });
165
+ });
166
+
167
+ test("no task queue rides on the Op (#2118)", () => {
168
+ expect(props(base)).not.toHaveProperty("taskQueue");
169
+ });
170
+ });
@@ -0,0 +1,197 @@
1
+ /**
2
+ * TerraformAdoptOp composite (#2105): claim the live resources a choudoufu
3
+ * estate's configuration already describes.
4
+ *
5
+ * The reconcile position on the lifecycle dial, for the one backend that has
6
+ * a typed path from live back to the configuration. chant #2089 deferred
7
+ * reconcile for the terraform lexicon because stock Terraform has none: a live
8
+ * resource nobody has imported is knowable only as HCL somebody writes, and
9
+ * generating that is a different problem. choudoufu changes the question
10
+ * rather than answering the old one. Ownership there is two tags on the
11
+ * resource (`live/MARKERS.md`), `live-plan` already names every live resource
12
+ * that exactly matches a declared block and carries no marker, and it prints
13
+ * the two values that would claim it. Cloud-to-code here is "claim what the
14
+ * configuration already describes" — a tag write, not a regeneration.
15
+ *
16
+ * Phases: Check, Ledger, Gate, Adopt.
17
+ *
18
+ * - **Check** runs `live-check`, which makes no cloud calls and says whether
19
+ * this configuration can move under markers at all. It runs first because
20
+ * a root that `live-check` refuses will produce an adoption ledger that
21
+ * reads as authoritative and is not, and the cheapest place to find that
22
+ * out is before the estate-wide sweep.
23
+ * - **Ledger** runs `live-plan` with `adoptionOnly`, so the run's human half
24
+ * is choudoufu's own adoption ledger (GitHub issue #587) and its machine
25
+ * half is the bound/omissions/unowned document (#788). The ledger is the
26
+ * approval subject; the document is what the Adopt step acts on.
27
+ * - **Gate** always. Adoption writes tags onto resources this estate does
28
+ * not yet own, which is the moment the estate's boundary moves, and no
29
+ * reading of that is routine enough to skip. There is no `gate: "never"`
30
+ * here, unlike `TerraformApplyOp`, so every run stops at the gate until
31
+ * someone has approved it. A gate is a fact on the gate ledger (#2119):
32
+ * `chant run` records that the run is waiting, ends with status `gated`
33
+ * and exits 3, `chant approve <op> <gate>` writes the resolution, and the
34
+ * next run reads it and walks through to the Adopt phase.
35
+ * - **Adopt** writes the markers. See `choudoufuAdopt`'s own doc for the
36
+ * mechanism and why it is the tag write rather than choudoufu's `adopt`
37
+ * policy verb.
38
+ *
39
+ * ## The adopt mechanism, and why it is not the policy verb
40
+ *
41
+ * choudoufu's `policy` block has an `adopt` verb for the declared-but-unmarked
42
+ * quadrant, and its migration guide names `policy { declared_untagged =
43
+ * "adopt" }` as the way to claim a whole estate at once. That is the smaller
44
+ * write, and #2105 tried to reach it from outside the configuration. It is not
45
+ * reachable: a `live` block in a terraform override file is dropped in silence
46
+ * (choudoufu's `Module.mergeFile` has no `Lives` case), a second primary file
47
+ * carrying one is refused as a duplicate, and a sidecar beside an in-block
48
+ * form is refused as two sources of truth. What is left is editing the root's
49
+ * own checked-in live configuration around an apply, which leaves an estate
50
+ * declaring "adopt everything declared and unmarked" if the run dies between
51
+ * the two edits.
52
+ *
53
+ * So the mechanism is the tag write, which choudoufu's migration guide calls
54
+ * the whole contract: "There is no `choudoufu adopt` command and no need for
55
+ * one. Two tags is the whole contract, so any tool that writes two tags can
56
+ * adopt a resource." Each write runs the paste-ready command choudoufu itself
57
+ * printed in the ledger, carrying its own provider configuration's region and
58
+ * endpoint. `choudoufuAdopt` has the measurements behind that ruling.
59
+ *
60
+ * ## Ambiguity is reported, never resolved
61
+ *
62
+ * Two live resources at one declared identity are two `unowned` entries with
63
+ * the same address, and no single tag write claims that address. Those
64
+ * candidates never enter the adoptable set; the Adopt step is handed them
65
+ * separately so the Op's own result names what it declined and why, beside
66
+ * what it wrote.
67
+ *
68
+ * ## Compensation
69
+ *
70
+ * Refused without a command, the same stance `TerraformApplyOp` takes.
71
+ * Un-adopting is `untag`: dropping this estate's marker from a live resource,
72
+ * which is a decision about the estate rather than something chant can
73
+ * synthesize. It is also not always the right undo — a resource that was
74
+ * already carrying the markers before this run is not one this run adopted.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * import { TerraformAdoptOp } from "@intentius/chant-lexicon-terraform";
79
+ *
80
+ * export const { op } = TerraformAdoptOp({ name: "estate-adopt", root: "estate" });
81
+ * ```
82
+ */
83
+
84
+ import { Op, phase, gate, activity, OpResource } from "@intentius/chant/op";
85
+ import {
86
+ choudoufuLiveCheck as checkStep,
87
+ choudoufuLivePlan as ledgerStep,
88
+ choudoufuAdopt as adoptStep,
89
+ } from "../op/builders";
90
+
91
+ export interface TerraformAdoptOpConfig {
92
+ /** Op name (kebab-case). `signalName` defaults to `approve-<name>`. */
93
+ name: string;
94
+ /** Key into the project's `terraform.roots`. Must be a live root: choudoufu, with a declared estate. */
95
+ root: string;
96
+ /**
97
+ * The estate whose markers this run looks for. Omitted, `choudoufuLivePlan`
98
+ * auto-detects it from the root's own `live` block or `estate.chdf.hcl`
99
+ * sidecar, which is the usual case.
100
+ */
101
+ estate?: string;
102
+ /** Gate signal name. Default: `approve-<name>`, as `TerraformApplyOp` does. */
103
+ signalName?: string;
104
+ /** How long a recorded pending gate stays valid, as a duration string. Default: core's own (48h). */
105
+ gateTimeout?: string;
106
+ /** Override the gate description shown to the approver. */
107
+ gateDescription?: string;
108
+ /**
109
+ * Directory each step starts the `chant.config.*` search from, which is what
110
+ * `terraform.roots` and the root's relative `dir` resolve against. Default:
111
+ * the running process's cwd.
112
+ */
113
+ cwd?: string;
114
+ /**
115
+ * Saga-style rollback on a failed adoption, run as an `onFailure` phase.
116
+ *
117
+ * Refused without a command. Undoing an adoption is `untag` — removing this
118
+ * estate's marker from a live resource — which is a decision about the
119
+ * estate, and one this Op cannot even scope correctly: a resource that
120
+ * already carried the markers is not one this run claimed. So `true`, or an
121
+ * object with no `command`, throws when the Op is built, naming the Op,
122
+ * rather than warning at the moment a rollback is already wanted.
123
+ */
124
+ compensate?: boolean | { command?: string };
125
+ }
126
+
127
+ export interface TerraformAdoptOpResources {
128
+ /** Op resource. Generates the Check/Ledger/Gate/Adopt workflow. */
129
+ op: InstanceType<typeof OpResource>;
130
+ }
131
+
132
+ export function TerraformAdoptOp(config: TerraformAdoptOpConfig): TerraformAdoptOpResources {
133
+ const compensateCommand = typeof config.compensate === "object" ? config.compensate.command : undefined;
134
+ if (config.compensate !== undefined && config.compensate !== false && compensateCommand === undefined) {
135
+ throw new Error(
136
+ `TerraformAdoptOp "${config.name}": compensate is enabled, but there is no automatic undo for an ` +
137
+ `adoption — un-adopting is untag, removing this estate's ownership marker from a live resource, ` +
138
+ `which is a decision about the estate rather than something chant can synthesize, and which this ` +
139
+ `Op cannot scope for you (a resource that already carried the markers is not one this run ` +
140
+ `claimed). Either supply compensate: { command: "..." } with an undo of your own, or set ` +
141
+ `compensate: false.`,
142
+ );
143
+ }
144
+
145
+ const where = config.cwd ? { cwd: config.cwd } : {};
146
+ const estate = config.estate ? { estate: config.estate } : {};
147
+
148
+ const check = checkStep(config.root, { ...where });
149
+ check.outcomeAttribute = { name: "Refused", from: "refused" };
150
+
151
+ // `id` is what makes `.out` legal. The Adopt step below reads two fields of
152
+ // this one: the adoptable matches, and the contested candidates it must
153
+ // report and never write.
154
+ const ledger = ledgerStep(config.root, { ...where, ...estate, adoptionOnly: true, id: "ledger" });
155
+ ledger.outcomeAttribute = [
156
+ { name: "Adoptable", from: "adoptable" },
157
+ { name: "Ambiguous", from: "ambiguous" },
158
+ ];
159
+
160
+ const adopt = adoptStep(config.root, {
161
+ ...where,
162
+ adoptions: ledger.out.adoptions,
163
+ contested: ledger.out.contested,
164
+ });
165
+ adopt.outcomeAttribute = { name: "Adopted", from: "adoptedCount" };
166
+
167
+ const op = Op({
168
+ name: config.name,
169
+ overview: `Adopt the live resources the "${config.root}" choudoufu estate already declares`,
170
+ labels: {
171
+ Adopt: "true",
172
+ TerraformRoot: config.root,
173
+ TerraformMode: "live",
174
+ },
175
+ phases: [
176
+ phase("Check", [check]),
177
+ phase("Ledger", [ledger]),
178
+ phase("Gate", [
179
+ gate(config.signalName ?? `approve-${config.name}`, {
180
+ ...(config.gateTimeout ? { timeout: config.gateTimeout } : {}),
181
+ description:
182
+ config.gateDescription ??
183
+ `Approve adopting live resources into the "${config.root}" estate. The Ledger phase's ` +
184
+ `adoption ledger is what is being approved: every resource it lists as adoptable gets this ` +
185
+ `estate's tofu-estate and tofu-address tags written onto it. The Adoptable and Ambiguous ` +
186
+ `run outcomes from that phase are its counts; an ambiguous address is never adopted.`,
187
+ }),
188
+ ]),
189
+ phase("Adopt", [adopt]),
190
+ ],
191
+ ...(compensateCommand
192
+ ? { onFailure: [phase("Rollback", [activity("shellCmd", { cmd: compensateCommand })])] }
193
+ : {}),
194
+ });
195
+
196
+ return { op };
197
+ }