@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
@@ -45,7 +45,7 @@ describe("TerraformWatchOp phases (#2087)", () => {
45
45
  }
46
46
  });
47
47
 
48
- test("the Plan step reports drift as a search attribute", () => {
48
+ test("the Plan step reports drift as a run outcome", () => {
49
49
  const plan = props({ name: "app-watch", root: "app" }).phases[1].steps[0] as ActivityStep;
50
50
  expect(plan.fn).toBe("terraformPlan");
51
51
  expect(plan.id).toBe("plan");
@@ -145,40 +145,99 @@ describe("TerraformWatchOp posts the human plan and never the plan JSON (#2087)"
145
145
  });
146
146
  });
147
147
 
148
- describe("TerraformWatchOp schedule (#2087)", () => {
149
- test("omitting `schedule` returns no schedule", () => {
150
- const result = TerraformWatchOp({ name: "app-watch", root: "app" });
151
- expect(result.schedule).toBeUndefined();
152
- expect(Object.prototype.hasOwnProperty.call(result, "schedule")).toBe(false);
148
+ describe("TerraformWatchOp on a live root (#2105)", () => {
149
+ const live = (over: Partial<Parameters<typeof TerraformWatchOp>[0]> = {}): OpConfig =>
150
+ props({ name: "estate-watch", root: "estate", live: true, ...over });
151
+
152
+ test("the Plan step is choudoufuLivePlan, and Init is unchanged", () => {
153
+ const op = live();
154
+ expect(phaseNames(op)).toEqual(["Init", "Plan"]);
155
+ expect(steps(op).map((s) => s.fn)).toEqual(["terraformInit", "choudoufuLivePlan"]);
153
156
  });
154
157
 
155
- test("a cron returns a Temporal::Schedule beside the Op", () => {
156
- const { op, schedule } = TerraformWatchOp({
157
- name: "app-watch",
158
- root: "app",
159
- schedule: "0 6 * * *",
158
+ test("one live read publishes Drift, Unowned and Adoptable", () => {
159
+ const plan = live().phases[1].steps[0] as ActivityStep;
160
+ expect(plan.id).toBe("plan");
161
+ // `drift` off `-detailed-exitcode`, `unowned`/`adoptable` off the same
162
+ // run's #788 document: three answers, one estate-wide sweep.
163
+ expect(plan.outcomeAttribute).toEqual([
164
+ { name: "Drift", from: "drift" },
165
+ { name: "Unowned", from: "unowned" },
166
+ { name: "Adoptable", from: "adoptable" },
167
+ ]);
168
+ });
169
+
170
+ test("the Op is marked live, and no plan file is named anywhere", () => {
171
+ const op = live();
172
+ expect(op.labels).toMatchObject({ TerraformMode: "live" });
173
+ expect(JSON.stringify(op)).not.toContain("chant.tfplan");
174
+ });
175
+
176
+ test("planFile on a live root is refused at build time, quoting choudoufu", () => {
177
+ expect(() => live({ planFile: "estate.tfplan" })).toThrow(/planFile is refused on a live root/);
178
+ });
179
+
180
+ test("estate off a live root is refused: there is no live plan to name one for", () => {
181
+ expect(() => props({ name: "app-watch", root: "app", estate: "prod" })).toThrow(/not on a live root/);
182
+ });
183
+
184
+ test("an explicit estate rides the Plan step", () => {
185
+ const plan = live({ estate: "prod-networking" }).phases[1].steps[0] as ActivityStep;
186
+ expect(plan.args?.estate).toBe("prod-networking");
187
+ });
188
+
189
+ test("a stock root is untouched: still terraformPlan, still one Drift attribute", () => {
190
+ const plan = props({ name: "app-watch", root: "app" }).phases[1].steps[0] as ActivityStep;
191
+ expect(plan.fn).toBe("terraformPlan");
192
+ expect(plan.outcomeAttribute).toEqual({ name: "Drift", from: "changed" });
193
+ });
194
+
195
+ for (const mode of ["issue", "pull-request"] as const) {
196
+ test(`${mode}: the body is the plan text plus the adoption ledger, as one field`, () => {
197
+ const op = live({ findingMode: mode });
198
+ expect(phaseNames(op)).toEqual(["Init", "Plan", "Report"]);
199
+ const report = op.phases[2].steps[0] as ActivityStep;
200
+ expect(report.fn).toBe("reconcilePr");
201
+ expect(isStepOutputRef(report.args?.body)).toBe(true);
202
+ expect(report.args?.body).toMatchObject({ step: "plan", path: "finding" });
160
203
  });
161
- expect(schedule).toBeDefined();
162
- expect(schedule!.entityType).toBe("Temporal::Schedule");
163
- expect(schedule!.lexicon).toBe("temporal");
164
- expect((schedule as unknown as { props: Record<string, unknown> }).props).toEqual({
165
- scheduleId: "app-watch-schedule",
166
- spec: { cronExpressions: ["0 6 * * *"] },
167
- action: { workflowType: "appWatchWorkflow", taskQueue: "app-watch" },
204
+
205
+ test(`${mode}: no reference to the live-plan document reaches the body`, () => {
206
+ // On a live root the JSON is choudoufu's bound/omissions/unowned
207
+ // document: live identities and tag values for every resource the
208
+ // estate touched. Same rule as the stock plan's JSON, same test.
209
+ const op = live({ findingMode: mode });
210
+ const report = op.phases[2].steps[0] as ActivityStep;
211
+ const paths = collectStepOutputRefs(report.args).map((r) => r.path);
212
+ expect(paths).toEqual(["finding"]);
213
+ expect(paths).not.toContain("json");
214
+ expect(JSON.stringify(report.args)).not.toContain("json");
168
215
  });
169
- expect((op as unknown as { props: OpConfig }).props.taskQueue).toBe("app-watch");
216
+ }
217
+
218
+ test("the whole live Op references exactly one field of the plan", () => {
219
+ const op = live({ findingMode: "issue" });
220
+ const refs = op.phases.flatMap((p) => p.steps.filter(isActivity)).flatMap((s) => collectStepOutputRefs(s.args));
221
+ expect(refs).toHaveLength(1);
222
+ expect(refs[0]).toMatchObject({ step: "plan", path: "finding" });
223
+ });
224
+ });
225
+
226
+ describe("TerraformWatchOp schedule (#2087, #2120)", () => {
227
+ test("omitting `schedule` leaves the Op with no cadence", () => {
228
+ const { op } = TerraformWatchOp({ name: "app-watch", root: "app" });
229
+ expect((op as unknown as { props: OpConfig }).props).not.toHaveProperty("schedule");
170
230
  });
171
231
 
172
- test("an explicit taskQueue reaches both the Op and the schedule action", () => {
173
- const { op, schedule } = TerraformWatchOp({
232
+ test("a cron lands on the Op itself", () => {
233
+ const { op } = TerraformWatchOp({
174
234
  name: "app-watch",
175
235
  root: "app",
176
236
  schedule: "0 6 * * *",
177
- taskQueue: "infra",
178
237
  });
179
- expect((op as unknown as { props: OpConfig }).props.taskQueue).toBe("infra");
180
- expect(
181
- ((schedule as unknown as { props: { action: { taskQueue: string } } }).props.action).taskQueue,
182
- ).toBe("infra");
238
+ expect((op as unknown as { props: OpConfig }).props.schedule).toEqual({
239
+ cron: "0 6 * * *",
240
+ overlap: "skip",
241
+ });
183
242
  });
184
243
  });
@@ -7,7 +7,7 @@
7
7
  * the plan found. `terraform plan -detailed-exitcode` is the whole mechanism.
8
8
  * Exit 0 is "the world matches the configuration", exit 2 is drift, and
9
9
  * `terraformPlan` turns that into the boolean `changed`, which rides out of
10
- * the Op as the `Drift` search attribute.
10
+ * the Op as the run's `Drift` outcome.
11
11
  *
12
12
  * Phases: Init, Plan, and (for a finding mode that opens something) Report.
13
13
  *
@@ -23,37 +23,55 @@
23
23
  * post the JSON, and `terraform-watch-op.test.ts` asserts no reference to it
24
24
  * reaches the finding step.
25
25
  *
26
+ * The same rule holds on a live root, where the JSON is choudoufu's own
27
+ * bound/omissions/unowned document rather than a plan representation, and
28
+ * carries live identities and tag values for every resource the estate
29
+ * touched. `choudoufuLivePlan` composes the plan text and the adoption ledger
30
+ * into one `finding` string for exactly this reason: the body is one
31
+ * reference to one text field, so there is no arrangement of the args in
32
+ * which the document reaches a body.
33
+ *
34
+ * ## A live root reports three things, not one (#2105)
35
+ *
36
+ * `live: true` swaps the Plan step for `choudoufuLivePlan`. On a choudoufu
37
+ * root ownership is a pair of tags on the resource rather than an entry in a
38
+ * state file, so a plan against the live system can answer three questions
39
+ * where a stock plan answers one: whether the estate drifted (the
40
+ * `-detailed-exitcode` exit, `Drift` as before), how many live resources sit
41
+ * at a declared identity carrying no marker (`Unowned`), and how many of those
42
+ * an exact content match makes claimable (`Adoptable`). All three come off a
43
+ * single live read, published as three run outcomes from the one step.
44
+ *
45
+ * The finding modes then carry the adoption ledger under the plan text: one
46
+ * line per adoptable match with its address, its live identity and the
47
+ * `tofu-estate`/`tofu-address` values that claim it, in the row form
48
+ * `live-plan -adoption-only` prints, followed by any contested address. That
49
+ * is a report, not an action. `TerraformAdoptOp` next door is what writes the
50
+ * markers, and it gates first.
51
+ *
26
52
  * ## Findings reuse `reconcilePr`
27
53
  *
28
- * `issue` and `pull-request` call the temporal lexicon's `reconcilePr`
29
- * activity, the one place in chant that shells to `gh issue create` /
30
- * `gh pr create`, rather than growing a second copy of those calls here.
54
+ * `issue` and `pull-request` call core's `reconcilePr` activity, the one place
55
+ * in chant that shells to `gh issue create` / `gh pr create`, rather than
56
+ * growing a second copy of those calls here.
31
57
  * That activity built its own change-set summary and had no way to be handed
32
58
  * one, so #2087 added a single field to it (`ReconcilePrArgs.body`), which is
33
59
  * what carries the `-no-color` plan through. Note that the pull-request mode
34
60
  * of `reconcilePr` regenerates chant TypeScript via `chant import`; a
35
- * terraform-native regeneration of HCL from live state is #2089, so until
36
- * then `findingMode: "issue"` is the mode with an end-to-end answer and
37
- * `"pull-request"` opens a PR whose body is the plan.
61
+ * terraform-native regeneration of HCL from live state is #2089, so
62
+ * `findingMode: "issue"` is the mode with an end-to-end answer and
63
+ * `"pull-request"` opens a PR whose body is the plan. On a live root #2089's
64
+ * question has a different answer rather than a pending one, and it is not
65
+ * regeneration: see `TerraformAdoptOp`.
38
66
  *
39
67
  * ## The schedule
40
68
  *
41
- * A cron makes this a `{ op, schedule }` pair, the shape
42
- * `lexicons/temporal/src/composites/workflow-audit-op.ts` and
43
- * `reconcile-op.ts` return. The schedule resource is `Temporal::Schedule`,
44
- * built here through core's own `createResource` rather than imported from
45
- * `@intentius/chant-lexicon-temporal`: no lexicon in this repo imports
46
- * another lexicon's package at runtime, and a static import would mean a
47
- * project that installs terraform and runs `chant run` on the local executor
48
- * could not load `@intentius/chant-lexicon-terraform` at all without also
49
- * installing temporal. The declarable is the same one temporal's
50
- * `TemporalSchedule` produces (`createResource("Temporal::Schedule",
51
- * "temporal", {})`, `lexicons/temporal/src/resources.ts`), so temporal's
52
- * serializer renders it unchanged when a project has both.
53
- *
54
- * A project with no Temporal at all runs the same Op on a CI cron instead:
55
- * `generateOpsPipeline` against the github lexicon turns the Op plus its cron
56
- * into a workflow. `examples/scheduled-watch/` is that recipe.
69
+ * A cron lands on the Op itself as `schedule` (#2120), the same field
70
+ * `WatchOp` and `ConvergeOp` set. It is runtime-neutral data: `chant
71
+ * operator` reads it as this Op's tick cadence, `generateOpsPipeline` against
72
+ * the github lexicon renders it as a CI cron, and a hosting lexicon hands it
73
+ * to its own scheduler. The one-shot `chant run app-watch` path ignores it.
74
+ * `examples/scheduled-watch/` is the CI recipe.
57
75
  *
58
76
  * @example
59
77
  * ```typescript
@@ -63,20 +81,30 @@
63
81
  * export const { op } = TerraformWatchOp({ name: "app-watch", root: "app" });
64
82
  *
65
83
  * // nightly, opening an issue when the plan is non-empty
66
- * export const { op, schedule } = TerraformWatchOp({
84
+ * export const { op } = TerraformWatchOp({
67
85
  * name: "app-watch",
68
86
  * root: "app",
69
87
  * schedule: "0 6 * * *",
70
88
  * findingMode: "issue",
71
89
  * });
90
+ *
91
+ * // a choudoufu estate: drift, unowned and adoptable, with the ledger in the issue
92
+ * export const { op } = TerraformWatchOp({
93
+ * name: "estate-watch",
94
+ * root: "estate",
95
+ * live: true,
96
+ * findingMode: "issue",
97
+ * });
72
98
  * ```
73
99
  */
74
100
 
75
101
  import { Op, phase, OpResource, type ActivityStep } from "@intentius/chant/op";
76
- import { createResource } from "@intentius/chant/runtime";
77
- import type { Declarable } from "@intentius/chant/declarable";
78
- import { DEFAULT_PLAN_FILE } from "../op/activities/terraform";
79
- import { terraformInit as initStep, terraformPlan as planStep } from "../op/builders";
102
+ import { CHOUDOUFU_LIVE_PLAN_OUT_REFUSAL, DEFAULT_PLAN_FILE } from "../op/activities/terraform";
103
+ import {
104
+ terraformInit as initStep,
105
+ terraformPlan as planStep,
106
+ choudoufuLivePlan as livePlanStep,
107
+ } from "../op/builders";
80
108
 
81
109
  /**
82
110
  * What to do with a non-empty plan. A subset of core's `OpFindingMode`
@@ -85,36 +113,47 @@ import { terraformInit as initStep, terraformPlan as planStep } from "../op/buil
85
113
  */
86
114
  export type TerraformFindingMode = "report" | "issue" | "pull-request";
87
115
 
88
- /**
89
- * `Temporal::Schedule`, built without importing the temporal lexicon. See the
90
- * module doc for why. Identical to `TemporalSchedule` in
91
- * `lexicons/temporal/src/resources.ts`.
92
- */
93
- const ScheduleResource = createResource("Temporal::Schedule", "temporal", {});
94
-
95
- /** `app-watch` becomes `appWatchWorkflow`, matching the temporal serializer's naming. */
96
- function kebabToCamel(s: string): string {
97
- return s.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
98
- }
99
-
100
116
  export interface TerraformWatchOpConfig {
101
- /** Op name (kebab-case). Also the default task queue and schedule id base. */
117
+ /** Op name (kebab-case). */
102
118
  name: string;
103
119
  /** Key into the project's `terraform.roots`. The root carries dir, workspace, var files and backend config. */
104
120
  root: string;
105
121
  /**
106
- * Cron expression. When set, a `Temporal::Schedule` is returned alongside
107
- * the Op; omit for one-shot `chant run` on the local executor, or for a CI
108
- * cron built by `generateOpsPipeline`.
122
+ * Cron expression. When set, it lands on the Op as `schedule` — the cadence
123
+ * `chant operator` ticks on and `generateOpsPipeline` renders as a CI cron.
124
+ * Omit for one-shot `chant run` on the local executor.
109
125
  */
110
126
  schedule?: string;
111
127
  /**
112
128
  * What to do when the plan proposes changes. Default: `"report"`, which
113
- * opens nothing. The `Drift` search attribute and the run's own log are
129
+ * opens nothing. The `Drift` run outcome and the run's own log are
114
130
  * the report.
115
131
  */
116
132
  findingMode?: TerraformFindingMode;
117
- /** Plan file the Plan step writes, relative to the root dir. Default: `chant.tfplan`. */
133
+ /**
134
+ * Watch a live root (#2105): the root runs `terraform.binary: "choudoufu"`
135
+ * and declares an estate, so the Plan phase is `choudoufuLivePlan` rather
136
+ * than `terraformPlan`. See the module doc for what changes.
137
+ *
138
+ * Declared here rather than detected, because a composite is built by
139
+ * `chant build` without reading a root module's `.tf` files. The activities
140
+ * detect the estate at run time (`op/activities/live-detect.ts`), but by then
141
+ * the phases are already serialized. Setting this on a stock root fails at
142
+ * the first step, with choudoufu's own reason.
143
+ */
144
+ live?: boolean;
145
+ /**
146
+ * The estate to plan against, for a live root whose estate this Op should
147
+ * name explicitly. Omitted, `choudoufuLivePlan` auto-detects it from the
148
+ * root's own `live` block or `estate.chdf.hcl` sidecar, which is the usual
149
+ * case. Refused on a stock root.
150
+ */
151
+ estate?: string;
152
+ /**
153
+ * Plan file the Plan step writes, relative to the root dir. Default:
154
+ * `chant.tfplan`. Refused on a live root, where choudoufu takes no plan file
155
+ * at all.
156
+ */
118
157
  planFile?: string;
119
158
  /** `-upgrade` on the Init step: re-resolve provider and module versions. */
120
159
  upgrade?: boolean;
@@ -129,27 +168,49 @@ export interface TerraformWatchOpConfig {
129
168
  title?: string;
130
169
  /** Branch the pull-request mode opens from. Default: `reconcilePr`'s own. */
131
170
  branch?: string;
132
- /** Override the task queue. Defaults to `name`. */
133
- taskQueue?: string;
134
171
  }
135
172
 
136
173
  export interface TerraformWatchOpResources {
137
- /** Op resource. Generates the Init/Plan/[Report] workflow. */
174
+ /** Op resource. Generates the Init/Plan/[Report] phases. */
138
175
  op: InstanceType<typeof OpResource>;
139
- /** `Temporal::Schedule`, present only when `schedule` was given. */
140
- schedule?: Declarable;
141
176
  }
142
177
 
143
178
  export function TerraformWatchOp(config: TerraformWatchOpConfig): TerraformWatchOpResources {
144
- const taskQueue = config.taskQueue ?? config.name;
145
- const planFile = config.planFile ?? DEFAULT_PLAN_FILE;
146
179
  const findingMode: TerraformFindingMode = config.findingMode ?? "report";
147
180
  const where = config.cwd ? { cwd: config.cwd } : {};
148
181
 
149
- // `id` is what makes `plan.out` legal, and `plan.out.text` is the only
150
- // channel by which any part of the plan leaves this Op.
151
- const plan = planStep(config.root, { planFile, ...where, id: "plan" });
152
- plan.outcomeAttribute = { name: "Drift", from: "changed" };
182
+ if (config.live && config.planFile !== undefined) {
183
+ throw new Error(
184
+ `TerraformWatchOp "${config.name}": planFile is refused on a live root. choudoufu: ` +
185
+ `"${CHOUDOUFU_LIVE_PLAN_OUT_REFUSAL}". A live plan writes no saved plan, so there is no file to name; ` +
186
+ "drop planFile, or drop live if this root runs stock.",
187
+ );
188
+ }
189
+ if (!config.live && config.estate !== undefined) {
190
+ throw new Error(
191
+ `TerraformWatchOp "${config.name}": estate names the estate a live plan looks for markers of, and ` +
192
+ "this Op is not on a live root. Set live: true, or drop estate.",
193
+ );
194
+ }
195
+
196
+ // `id` is what makes `plan.out` legal, and one field of `plan.out` is the
197
+ // only channel by which any part of the plan leaves this Op.
198
+ //
199
+ // A live root reads the live system instead of a state file, so the three
200
+ // things a watch reports come off one `choudoufuLivePlan`: `drift` from
201
+ // `-detailed-exitcode`, and `unowned`/`adoptable` from the `-json` document
202
+ // the same run wrote. Three attributes, one live read: see
203
+ // `ActivityStep.outcomeAttribute`'s array form (#2105).
204
+ const plan = config.live
205
+ ? livePlanStep(config.root, { ...where, ...(config.estate ? { estate: config.estate } : {}), id: "plan" })
206
+ : planStep(config.root, { planFile: config.planFile ?? DEFAULT_PLAN_FILE, ...where, id: "plan" });
207
+ plan.outcomeAttribute = config.live
208
+ ? [
209
+ { name: "Drift", from: "drift" },
210
+ { name: "Unowned", from: "unowned" },
211
+ { name: "Adoptable", from: "adoptable" },
212
+ ]
213
+ : { name: "Drift", from: "changed" };
153
214
 
154
215
  const phases = [
155
216
  phase("Init", [initStep(config.root, { ...where, ...(config.upgrade ? { upgrade: true } : {}) })]),
@@ -169,7 +230,11 @@ export function TerraformWatchOp(config: TerraformWatchOpConfig): TerraformWatch
169
230
  mode: findingMode,
170
231
  entries: [],
171
232
  title: config.title ?? `Terraform drift in root "${config.root}"`,
172
- body: plan.out.text,
233
+ // Stock: the `-no-color` plan. Live: the same plan text with the
234
+ // adoption ledger under it, which `choudoufuLivePlan` composes into
235
+ // one `finding` field precisely so that a body is one reference and
236
+ // the `-json` document has no path into one at all.
237
+ body: config.live ? plan.out.finding : plan.out.text,
173
238
  ...(config.branch ? { branch: config.branch } : {}),
174
239
  },
175
240
  outcomeAttribute:
@@ -180,25 +245,17 @@ export function TerraformWatchOp(config: TerraformWatchOpConfig): TerraformWatch
180
245
 
181
246
  const op = Op({
182
247
  name: config.name,
183
- overview: `Plan the "${config.root}" terraform root and report drift`,
184
- taskQueue,
185
- searchAttributes: {
248
+ overview: config.live
249
+ ? `Live-plan the "${config.root}" choudoufu estate and report drift, unowned and adoptable resources`
250
+ : `Plan the "${config.root}" terraform root and report drift`,
251
+ labels: {
186
252
  Watch: "true",
187
253
  TerraformRoot: config.root,
254
+ ...(config.live ? { TerraformMode: "live" } : {}),
188
255
  },
256
+ ...(config.schedule ? { schedule: { cron: config.schedule, overlap: "skip" as const } } : {}),
189
257
  phases,
190
258
  });
191
259
 
192
- if (!config.schedule) return { op };
193
-
194
- const schedule = new ScheduleResource({
195
- scheduleId: `${config.name}-schedule`,
196
- spec: { cronExpressions: [config.schedule] },
197
- action: {
198
- workflowType: kebabToCamel(config.name) + "Workflow",
199
- taskQueue,
200
- },
201
- });
202
-
203
- return { op, schedule };
260
+ return { op };
204
261
  }
package/src/config.ts CHANGED
@@ -25,6 +25,20 @@
25
25
  import { z } from "zod";
26
26
  import type { ChantConfig } from "@intentius/chant/config";
27
27
 
28
+ /**
29
+ * chant's cross-lexicon delete-mode vocabulary (`lexicons/k8s/src/op/
30
+ * activities/kubectl.ts`'s `ApplyDeleteMode`, `packages/core/src/op/
31
+ * activities/apply.ts`'s `DeleteMode`), read here for a live root and mapped
32
+ * onto choudoufu's `policy` block (#2106): `"never"` requires the root's
33
+ * `policy` to set `undeclared_tagged` to `"keep"`, `"untag"` or `"report"`
34
+ * (TF026 enforces this at build time); `"owned-only"` is choudoufu's own
35
+ * default verb for that quadrant (`delete`) and needs nothing; `"gated"`
36
+ * needs nothing beyond `TerraformApplyOp`'s own approval gate. Inert on a
37
+ * stock root: choudoufu's `policy` block does not exist there.
38
+ */
39
+ export const terraformDeleteModeSchema = z.enum(["never", "owned-only", "gated"]);
40
+ export type TerraformDeleteMode = z.infer<typeof terraformDeleteModeSchema>;
41
+
28
42
  /**
29
43
  * One Terraform root module chant reads. `dir` is the only required field:
30
44
  * everything else names how the root is invoked rather than what it declares.
@@ -43,14 +57,29 @@ export const terraformRootSchema = z.strictObject({
43
57
  varFiles: z.array(z.string()).optional(),
44
58
  /** `-backend-config` key/value pairs handed to `init`. */
45
59
  backendConfig: z.record(z.string(), z.string()).optional(),
60
+ /** This root's declared delete mode. See {@link terraformDeleteModeSchema}. */
61
+ delete: terraformDeleteModeSchema.optional(),
46
62
  });
47
63
 
48
64
  export const terraformConfigSchema = z.strictObject({
49
65
  /**
50
- * Which CLI drives the roots. The two are wire-compatible for everything
51
- * this lexicon does, so the choice is recorded rather than inferred.
66
+ * Which CLI drives the roots. `terraform` and `tofu` are wire-compatible
67
+ * for everything this lexicon does, so the choice is recorded rather than
68
+ * inferred. `choudoufu` (#2103) is an OpenTofu fork: a root running it also
69
+ * needs a declared estate (a `live` block or an `estate.chdf.hcl` sidecar,
70
+ * seen by the shared HCL parse) before it counts as live. Declaring the
71
+ * binary alone runs the root stock, exactly as `terraform`/`tofu` do.
72
+ */
73
+ binary: z.enum(["terraform", "tofu", "choudoufu"]).optional(),
74
+ /**
75
+ * How far a root's parse follows its `module` calls (#2112), spelled the
76
+ * way tflint spells it. `local` (the default) reads modules sourced from a
77
+ * relative path, so TF014, TF015 and TF020 see inside them; `none` reads
78
+ * only each root's own directory; `all` is reserved for fetching registry
79
+ * and git modules and is refused with a message, since chant fetches
80
+ * nothing. See `hcl/descend.ts`.
52
81
  */
53
- binary: z.enum(["terraform", "tofu"]).optional(),
82
+ callModuleType: z.enum(["local", "none", "all"]).optional(),
54
83
  /** Named root modules. The name is the entity-key prefix, so keep it stable. */
55
84
  roots: z.record(z.string(), terraformRootSchema),
56
85
  });