@intentius/chant-lexicon-terraform 0.124.52 → 0.124.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (641) hide show
  1. package/README.md +14 -10
  2. package/dist/__fixtures__/pr-loop/estate.component.d.ts +7 -0
  3. package/dist/__fixtures__/pr-loop/estate.component.d.ts.map +1 -0
  4. package/dist/__fixtures__/pr-loop/estate.component.js +7 -0
  5. package/dist/__fixtures__/report/generate.d.ts +26 -0
  6. package/dist/__fixtures__/report/generate.d.ts.map +1 -0
  7. package/dist/__fixtures__/report/generate.js +58 -0
  8. package/dist/applied-state.d.ts +27 -0
  9. package/dist/applied-state.d.ts.map +1 -0
  10. package/dist/applied-state.js +44 -0
  11. package/dist/backend-config.d.ts +53 -0
  12. package/dist/backend-config.d.ts.map +1 -0
  13. package/dist/backend-config.js +136 -0
  14. package/dist/behaviour/kinds.d.ts.map +1 -1
  15. package/dist/behaviour/kinds.js +4 -0
  16. package/dist/behaviour/predict.d.ts.map +1 -1
  17. package/dist/behaviour/predict.js +6 -2
  18. package/dist/behaviour/reference-catalog.d.ts +16 -5
  19. package/dist/behaviour/reference-catalog.d.ts.map +1 -1
  20. package/dist/behaviour/reference-catalog.js +16 -5
  21. package/dist/behaviour/request.d.ts.map +1 -1
  22. package/dist/behaviour/request.js +3 -2
  23. package/dist/changed-roots.d.ts +6 -4
  24. package/dist/changed-roots.d.ts.map +1 -1
  25. package/dist/changed-roots.js +14 -18
  26. package/dist/ci-setup.d.ts +11 -0
  27. package/dist/ci-setup.d.ts.map +1 -0
  28. package/dist/ci-setup.js +41 -0
  29. package/dist/commands.d.ts.map +1 -1
  30. package/dist/commands.js +4 -3
  31. package/dist/components/capability-plugin.d.ts +105 -9
  32. package/dist/components/capability-plugin.d.ts.map +1 -1
  33. package/dist/components/capability-plugin.js +357 -36
  34. package/dist/components/compose-group.d.ts +145 -0
  35. package/dist/components/compose-group.d.ts.map +1 -0
  36. package/dist/components/compose-group.js +407 -0
  37. package/dist/components/placeholder-preview.d.ts +78 -0
  38. package/dist/components/placeholder-preview.d.ts.map +1 -0
  39. package/dist/components/placeholder-preview.js +254 -0
  40. package/dist/components/release-inputs.d.ts +29 -0
  41. package/dist/components/release-inputs.d.ts.map +1 -0
  42. package/dist/components/release-inputs.js +57 -0
  43. package/dist/components/trace-facts.d.ts +16 -0
  44. package/dist/components/trace-facts.d.ts.map +1 -0
  45. package/dist/components/trace-facts.js +19 -0
  46. package/dist/compose/apply-events.d.ts +46 -0
  47. package/dist/compose/apply-events.d.ts.map +1 -0
  48. package/dist/compose/apply-events.js +77 -0
  49. package/dist/compose/estate.d.ts +194 -0
  50. package/dist/compose/estate.d.ts.map +1 -0
  51. package/dist/compose/estate.js +363 -0
  52. package/dist/compose/hcl.d.ts +99 -0
  53. package/dist/compose/hcl.d.ts.map +1 -0
  54. package/dist/compose/hcl.js +302 -0
  55. package/dist/compose/module-address.d.ts +24 -0
  56. package/dist/compose/module-address.d.ts.map +1 -0
  57. package/dist/compose/module-address.js +43 -0
  58. package/dist/compose/root.d.ts +127 -0
  59. package/dist/compose/root.d.ts.map +1 -0
  60. package/dist/compose/root.js +586 -0
  61. package/dist/compose/run.d.ts +183 -0
  62. package/dist/compose/run.d.ts.map +1 -0
  63. package/dist/compose/run.js +447 -0
  64. package/dist/compose/slice.d.ts +102 -0
  65. package/dist/compose/slice.d.ts.map +1 -0
  66. package/dist/compose/slice.js +249 -0
  67. package/dist/compose/state-address.d.ts +28 -0
  68. package/dist/compose/state-address.d.ts.map +1 -0
  69. package/dist/compose/state-address.js +154 -0
  70. package/dist/compose/state-merge.d.ts +52 -0
  71. package/dist/compose/state-merge.d.ts.map +1 -0
  72. package/dist/compose/state-merge.js +135 -0
  73. package/dist/compose.d.ts +179 -0
  74. package/dist/compose.d.ts.map +1 -0
  75. package/dist/compose.js +482 -0
  76. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  77. package/dist/composites/terraform-apply-op.js +7 -3
  78. package/dist/cone.d.ts +53 -0
  79. package/dist/cone.d.ts.map +1 -0
  80. package/dist/cone.js +119 -0
  81. package/dist/config.d.ts +59 -13
  82. package/dist/config.d.ts.map +1 -1
  83. package/dist/config.js +43 -9
  84. package/dist/cost-hold.d.ts +27 -0
  85. package/dist/cost-hold.d.ts.map +1 -0
  86. package/dist/cost-hold.js +21 -0
  87. package/dist/describe-resources.d.ts +74 -12
  88. package/dist/describe-resources.d.ts.map +1 -1
  89. package/dist/describe-resources.js +374 -66
  90. package/dist/drift-read.d.ts +33 -0
  91. package/dist/drift-read.d.ts.map +1 -0
  92. package/dist/drift-read.js +243 -0
  93. package/dist/drift-record.d.ts +147 -0
  94. package/dist/drift-record.d.ts.map +1 -0
  95. package/dist/drift-record.js +383 -0
  96. package/dist/drift-rows.d.ts +61 -0
  97. package/dist/drift-rows.d.ts.map +1 -0
  98. package/dist/drift-rows.js +108 -0
  99. package/dist/exit-codes.d.ts +55 -0
  100. package/dist/exit-codes.d.ts.map +1 -0
  101. package/dist/exit-codes.js +48 -0
  102. package/dist/hcl/edges.d.ts.map +1 -1
  103. package/dist/hcl/edges.js +24 -1
  104. package/dist/hcl/files.d.ts +61 -0
  105. package/dist/hcl/files.d.ts.map +1 -0
  106. package/dist/hcl/files.js +221 -0
  107. package/dist/hcl/instances.d.ts +82 -0
  108. package/dist/hcl/instances.d.ts.map +1 -0
  109. package/dist/hcl/instances.js +390 -0
  110. package/dist/hcl/parse.d.ts +81 -9
  111. package/dist/hcl/parse.d.ts.map +1 -1
  112. package/dist/hcl/parse.js +114 -16
  113. package/dist/hcl/roots.d.ts.map +1 -1
  114. package/dist/hcl/roots.js +5 -0
  115. package/dist/hcl/suppressions.d.ts.map +1 -1
  116. package/dist/hcl/suppressions.js +8 -2
  117. package/dist/index.d.ts +13 -2
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +24 -2
  120. package/dist/integrity.json +8 -5
  121. package/dist/lint/audit-catalog.d.ts.map +1 -1
  122. package/dist/lint/audit-catalog.js +27 -3
  123. package/dist/lint/audit-lineage.d.ts +14 -2
  124. package/dist/lint/audit-lineage.d.ts.map +1 -1
  125. package/dist/lint/post-synth/index.d.ts.map +1 -1
  126. package/dist/lint/post-synth/index.js +4 -0
  127. package/dist/lint/post-synth/record-store.d.ts +54 -0
  128. package/dist/lint/post-synth/record-store.d.ts.map +1 -0
  129. package/dist/lint/post-synth/record-store.js +104 -0
  130. package/dist/lint/post-synth/tf001.d.ts +2 -2
  131. package/dist/lint/post-synth/tf001.js +2 -2
  132. package/dist/lint/post-synth/tf024.d.ts +35 -11
  133. package/dist/lint/post-synth/tf024.d.ts.map +1 -1
  134. package/dist/lint/post-synth/tf024.js +92 -20
  135. package/dist/lint/post-synth/tf046.d.ts +64 -0
  136. package/dist/lint/post-synth/tf046.d.ts.map +1 -0
  137. package/dist/lint/post-synth/tf046.js +175 -0
  138. package/dist/lint/post-synth/tf047.d.ts +36 -0
  139. package/dist/lint/post-synth/tf047.d.ts.map +1 -0
  140. package/dist/lint/post-synth/tf047.js +94 -0
  141. package/dist/live-plan-reasons.d.ts +21 -0
  142. package/dist/live-plan-reasons.d.ts.map +1 -0
  143. package/dist/live-plan-reasons.js +47 -0
  144. package/dist/lock-rows.d.ts +36 -0
  145. package/dist/lock-rows.d.ts.map +1 -0
  146. package/dist/lock-rows.js +60 -0
  147. package/dist/lock-timeout.d.ts +22 -0
  148. package/dist/lock-timeout.d.ts.map +1 -0
  149. package/dist/lock-timeout.js +32 -0
  150. package/dist/lsp/option-keys.d.ts.map +1 -1
  151. package/dist/lsp/option-keys.js +2 -0
  152. package/dist/manifest.json +1 -1
  153. package/dist/module-catalog/index.d.ts +69 -0
  154. package/dist/module-catalog/index.d.ts.map +1 -0
  155. package/dist/module-catalog/index.js +110 -0
  156. package/dist/module-catalog/refresh-cli.d.ts +4 -0
  157. package/dist/module-catalog/refresh-cli.d.ts.map +1 -0
  158. package/dist/module-catalog/refresh-cli.js +64 -0
  159. package/dist/module-catalog/static-tier.d.ts +6 -0
  160. package/dist/module-catalog/static-tier.d.ts.map +1 -0
  161. package/dist/module-catalog/static-tier.js +1650 -0
  162. package/dist/module-catalog/types.d.ts +65 -0
  163. package/dist/module-catalog/types.d.ts.map +1 -0
  164. package/dist/module-catalog/types.js +33 -0
  165. package/dist/okf/index.md +3 -1
  166. package/dist/okf/rules/TF024.md +2 -2
  167. package/dist/okf/rules/TF046.md +11 -0
  168. package/dist/okf/rules/TF047.md +11 -0
  169. package/dist/op/activities/index.d.ts +6 -2
  170. package/dist/op/activities/index.d.ts.map +1 -1
  171. package/dist/op/activities/index.js +5 -1
  172. package/dist/op/activities/live-detect.d.ts +4 -3
  173. package/dist/op/activities/live-detect.d.ts.map +1 -1
  174. package/dist/op/activities/live-detect.js +57 -32
  175. package/dist/op/activities/pin-rollout.d.ts +2 -0
  176. package/dist/op/activities/pin-rollout.d.ts.map +1 -1
  177. package/dist/op/activities/pin-rollout.js +1 -0
  178. package/dist/op/activities/terraform.d.ts +182 -5
  179. package/dist/op/activities/terraform.d.ts.map +1 -1
  180. package/dist/op/activities/terraform.js +224 -24
  181. package/dist/op/activity-contracts.d.ts +27 -0
  182. package/dist/op/activity-contracts.d.ts.map +1 -1
  183. package/dist/op/activity-contracts.js +19 -1
  184. package/dist/op/builders.d.ts +7 -1
  185. package/dist/op/builders.d.ts.map +1 -1
  186. package/dist/op/builders.js +9 -0
  187. package/dist/pin/consumers.d.ts +35 -0
  188. package/dist/pin/consumers.d.ts.map +1 -0
  189. package/dist/pin/consumers.js +70 -0
  190. package/dist/pin/edit.d.ts.map +1 -1
  191. package/dist/pin/edit.js +35 -0
  192. package/dist/pin/rollout.d.ts +2 -0
  193. package/dist/pin/rollout.d.ts.map +1 -1
  194. package/dist/pin/rollout.js +14 -11
  195. package/dist/plan-shape.d.ts +30 -0
  196. package/dist/plan-shape.d.ts.map +1 -0
  197. package/dist/plan-shape.js +125 -0
  198. package/dist/plugin.d.ts.map +1 -1
  199. package/dist/plugin.js +37 -18
  200. package/dist/registry/address.d.ts +63 -0
  201. package/dist/registry/address.d.ts.map +1 -0
  202. package/dist/registry/address.js +65 -0
  203. package/dist/registry/catalog-read.d.ts +14 -0
  204. package/dist/registry/catalog-read.d.ts.map +1 -0
  205. package/dist/registry/catalog-read.js +58 -0
  206. package/dist/registry/deprecated.d.ts +37 -0
  207. package/dist/registry/deprecated.d.ts.map +1 -0
  208. package/dist/registry/deprecated.js +43 -0
  209. package/dist/registry/docs.d.ts +96 -0
  210. package/dist/registry/docs.d.ts.map +1 -0
  211. package/dist/registry/docs.js +210 -0
  212. package/dist/registry/edge/edge.d.ts +46 -0
  213. package/dist/registry/edge/edge.d.ts.map +1 -0
  214. package/dist/registry/edge/edge.js +194 -0
  215. package/dist/registry/edge/index.d.ts +11 -0
  216. package/dist/registry/edge/index.d.ts.map +1 -0
  217. package/dist/registry/edge/index.js +10 -0
  218. package/dist/registry/edge/lambda-edge.d.ts +76 -0
  219. package/dist/registry/edge/lambda-edge.d.ts.map +1 -0
  220. package/dist/registry/edge/lambda-edge.js +95 -0
  221. package/dist/registry/edge/sigv4-web.d.ts +33 -0
  222. package/dist/registry/edge/sigv4-web.d.ts.map +1 -0
  223. package/dist/registry/edge/sigv4-web.js +56 -0
  224. package/dist/registry/edge/token-source.d.ts +73 -0
  225. package/dist/registry/edge/token-source.d.ts.map +1 -0
  226. package/dist/registry/edge/token-source.js +115 -0
  227. package/dist/registry/edge/worker.d.ts +45 -0
  228. package/dist/registry/edge/worker.d.ts.map +1 -0
  229. package/dist/registry/edge/worker.js +26 -0
  230. package/dist/registry/handler.d.ts +25 -0
  231. package/dist/registry/handler.d.ts.map +1 -0
  232. package/dist/registry/handler.js +75 -0
  233. package/dist/registry/headers-file.d.ts +21 -0
  234. package/dist/registry/headers-file.d.ts.map +1 -0
  235. package/dist/registry/headers-file.js +57 -0
  236. package/dist/registry/index.d.ts +31 -0
  237. package/dist/registry/index.d.ts.map +1 -0
  238. package/dist/registry/index.js +30 -0
  239. package/dist/registry/mirror-paths.d.ts +9 -0
  240. package/dist/registry/mirror-paths.d.ts.map +1 -0
  241. package/dist/registry/mirror-paths.js +8 -0
  242. package/dist/registry/mirror.d.ts +130 -0
  243. package/dist/registry/mirror.d.ts.map +1 -0
  244. package/dist/registry/mirror.js +319 -0
  245. package/dist/registry/proxies.d.ts +73 -0
  246. package/dist/registry/proxies.d.ts.map +1 -0
  247. package/dist/registry/proxies.js +111 -0
  248. package/dist/registry/reader.d.ts +57 -0
  249. package/dist/registry/reader.d.ts.map +1 -0
  250. package/dist/registry/reader.js +198 -0
  251. package/dist/registry/semver.d.ts +16 -0
  252. package/dist/registry/semver.d.ts.map +1 -0
  253. package/dist/registry/semver.js +24 -0
  254. package/dist/registry/serve.d.ts +24 -0
  255. package/dist/registry/serve.d.ts.map +1 -0
  256. package/dist/registry/serve.js +64 -0
  257. package/dist/registry/source-forms.d.ts +79 -0
  258. package/dist/registry/source-forms.d.ts.map +1 -0
  259. package/dist/registry/source-forms.js +202 -0
  260. package/dist/registry/targets/artifactory.d.ts +28 -0
  261. package/dist/registry/targets/artifactory.d.ts.map +1 -0
  262. package/dist/registry/targets/artifactory.js +206 -0
  263. package/dist/registry/targets/fake-artifactory.d.ts +30 -0
  264. package/dist/registry/targets/fake-artifactory.d.ts.map +1 -0
  265. package/dist/registry/targets/fake-artifactory.js +142 -0
  266. package/dist/registry/targets/fake-gitlab.d.ts +39 -0
  267. package/dist/registry/targets/fake-gitlab.d.ts.map +1 -0
  268. package/dist/registry/targets/fake-gitlab.js +136 -0
  269. package/dist/registry/targets/fake-hcp.d.ts +50 -0
  270. package/dist/registry/targets/fake-hcp.d.ts.map +1 -0
  271. package/dist/registry/targets/fake-hcp.js +202 -0
  272. package/dist/registry/targets/fake-http.d.ts +31 -0
  273. package/dist/registry/targets/fake-http.d.ts.map +1 -0
  274. package/dist/registry/targets/fake-http.js +87 -0
  275. package/dist/registry/targets/fake-nexus.d.ts +27 -0
  276. package/dist/registry/targets/fake-nexus.d.ts.map +1 -0
  277. package/dist/registry/targets/fake-nexus.js +137 -0
  278. package/dist/registry/targets/gitlab.d.ts +53 -0
  279. package/dist/registry/targets/gitlab.d.ts.map +1 -0
  280. package/dist/registry/targets/gitlab.js +187 -0
  281. package/dist/registry/targets/hcp.d.ts +29 -0
  282. package/dist/registry/targets/hcp.d.ts.map +1 -0
  283. package/dist/registry/targets/hcp.js +310 -0
  284. package/dist/registry/targets/hosted.d.ts +62 -0
  285. package/dist/registry/targets/hosted.d.ts.map +1 -0
  286. package/dist/registry/targets/hosted.js +74 -0
  287. package/dist/registry/targets/index.d.ts +18 -0
  288. package/dist/registry/targets/index.d.ts.map +1 -0
  289. package/dist/registry/targets/index.js +17 -0
  290. package/dist/registry/targets/module-target.d.ts +68 -0
  291. package/dist/registry/targets/module-target.d.ts.map +1 -0
  292. package/dist/registry/targets/module-target.js +63 -0
  293. package/dist/registry/targets/nexus.d.ts +43 -0
  294. package/dist/registry/targets/nexus.d.ts.map +1 -0
  295. package/dist/registry/targets/nexus.js +250 -0
  296. package/dist/registry/targets/zip.d.ts +55 -0
  297. package/dist/registry/targets/zip.d.ts.map +1 -0
  298. package/dist/registry/targets/zip.js +278 -0
  299. package/dist/registry/writer.d.ts +101 -0
  300. package/dist/registry/writer.d.ts.map +1 -0
  301. package/dist/registry/writer.js +109 -0
  302. package/dist/report.d.ts +15 -6
  303. package/dist/report.d.ts.map +1 -1
  304. package/dist/report.js +28 -55
  305. package/dist/rules/record-store.ts +122 -0
  306. package/dist/rules/tf001.ts +2 -2
  307. package/dist/rules/tf024.ts +105 -22
  308. package/dist/rules/tf046.ts +204 -0
  309. package/dist/rules/tf047.ts +114 -0
  310. package/dist/skills/chant-terraform.md +1 -1
  311. package/dist/state/azure-blob.d.ts +9 -0
  312. package/dist/state/azure-blob.d.ts.map +1 -0
  313. package/dist/state/azure-blob.js +8 -0
  314. package/dist/state/backend.d.ts +158 -0
  315. package/dist/state/backend.d.ts.map +1 -0
  316. package/dist/state/backend.js +406 -0
  317. package/dist/state/exec.d.ts +39 -0
  318. package/dist/state/exec.d.ts.map +1 -0
  319. package/dist/state/exec.js +80 -0
  320. package/dist/state/gcs.d.ts +8 -0
  321. package/dist/state/gcs.d.ts.map +1 -0
  322. package/dist/state/gcs.js +7 -0
  323. package/dist/state/index.d.ts +15 -0
  324. package/dist/state/index.d.ts.map +1 -0
  325. package/dist/state/index.js +14 -0
  326. package/dist/state/locks.d.ts +105 -0
  327. package/dist/state/locks.d.ts.map +1 -0
  328. package/dist/state/locks.js +130 -0
  329. package/dist/state/object-store.d.ts +8 -0
  330. package/dist/state/object-store.d.ts.map +1 -0
  331. package/dist/state/object-store.js +7 -0
  332. package/dist/state/root-states.d.ts +131 -0
  333. package/dist/state/root-states.d.ts.map +1 -0
  334. package/dist/state/root-states.js +241 -0
  335. package/dist/state/s3.d.ts +21 -0
  336. package/dist/state/s3.d.ts.map +1 -0
  337. package/dist/state/s3.js +19 -0
  338. package/dist/state/store.d.ts +51 -0
  339. package/dist/state/store.d.ts.map +1 -0
  340. package/dist/state/store.js +185 -0
  341. package/dist/state-ops.d.ts +255 -0
  342. package/dist/state-ops.d.ts.map +1 -0
  343. package/dist/state-ops.js +305 -0
  344. package/dist/state-reads.d.ts +57 -0
  345. package/dist/state-reads.d.ts.map +1 -0
  346. package/dist/state-reads.js +104 -0
  347. package/dist/terragrunt/affected.d.ts.map +1 -1
  348. package/dist/terragrunt/affected.js +2 -1
  349. package/dist/used-sources.d.ts +54 -0
  350. package/dist/used-sources.d.ts.map +1 -0
  351. package/dist/used-sources.js +157 -0
  352. package/package.json +24 -4
  353. package/src/__fixtures__/ACCEPTANCE.md +69 -7
  354. package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
  355. package/src/__fixtures__/compose/state-app.json +66 -0
  356. package/src/__fixtures__/compose/state-edge.json +40 -0
  357. package/src/__fixtures__/compose/state-net.json +58 -0
  358. package/src/__fixtures__/compose/three-app.plan.json +184 -0
  359. package/src/__fixtures__/compose/three-composed.plan.json +509 -0
  360. package/src/__fixtures__/compose/three-edge.plan.json +144 -0
  361. package/src/__fixtures__/compose/three-net.plan.json +174 -0
  362. package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
  363. package/src/__fixtures__/compose/working-after-apply.json +145 -0
  364. package/src/__fixtures__/config-blocks/app/main.tf +61 -0
  365. package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
  366. package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
  367. package/src/__fixtures__/config-files/shadow/main.tf +3 -0
  368. package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
  369. package/src/__fixtures__/config-files/shadow/other.tf +3 -0
  370. package/src/__fixtures__/config-files/tf/main.tf +11 -0
  371. package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
  372. package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
  373. package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
  374. package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
  375. package/src/__fixtures__/drift-record/README.md +16 -0
  376. package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
  377. package/src/__fixtures__/drift-rows/live-root-plan.json +70 -0
  378. package/src/__fixtures__/drift-rows/refresh-plan.json +61 -0
  379. package/src/__fixtures__/instance-forms/app/main.tf +110 -0
  380. package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
  381. package/src/__fixtures__/instance-vars/app/main.tf +52 -0
  382. package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
  383. package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
  384. package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
  385. package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
  386. package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
  387. package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
  388. package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
  389. package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
  390. package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
  391. package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
  392. package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
  393. package/src/__fixtures__/pr-loop/README.md +50 -0
  394. package/src/__fixtures__/pr-loop/chant.config.json +13 -0
  395. package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
  396. package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
  397. package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
  398. package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
  399. package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
  400. package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
  401. package/src/__fixtures__/report/applied.show.json +51 -0
  402. package/src/__fixtures__/report/generate.ts +63 -0
  403. package/src/__fixtures__/report/plan.show.json +200 -0
  404. package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
  405. package/src/__fixtures__/report/tf-apply/report.json +338 -0
  406. package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
  407. package/src/__fixtures__/report/tf-plan/report.json +320 -0
  408. package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
  409. package/src/__fixtures__/used-sources/root/extra.tofu +1 -0
  410. package/src/__fixtures__/used-sources/root/main.tf +31 -0
  411. package/src/__fixtures__/used-sources/root/modules/net/main.tf +14 -0
  412. package/src/__fixtures__/used-sources/root/modules/net/versions.tf.json +7 -0
  413. package/src/__fixtures__/used-sources/unit/terragrunt.hcl +3 -0
  414. package/src/applied-state.ts +52 -0
  415. package/src/backend-config.test.ts +105 -0
  416. package/src/backend-config.ts +133 -0
  417. package/src/behaviour/kinds.ts +4 -0
  418. package/src/behaviour/predict.ts +5 -2
  419. package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
  420. package/src/behaviour/reference-catalog.ts +16 -5
  421. package/src/behaviour/request.ts +3 -2
  422. package/src/change-set-instances.test.ts +98 -0
  423. package/src/changed-roots.ts +14 -16
  424. package/src/ci-setup.ts +42 -0
  425. package/src/commands.ts +4 -3
  426. package/src/components/capability-plugin.test.ts +198 -19
  427. package/src/components/capability-plugin.ts +383 -35
  428. package/src/components/change-set-graph.e2e.test.ts +11 -5
  429. package/src/components/compose-group.test.ts +486 -0
  430. package/src/components/compose-group.ts +489 -0
  431. package/src/components/demo-composed-roots.e2e.test.ts +440 -0
  432. package/src/components/member-outputs.test.ts +76 -0
  433. package/src/components/placeholder-preview.test.ts +147 -0
  434. package/src/components/placeholder-preview.ts +257 -0
  435. package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
  436. package/src/components/pr-loop.tofu.test.ts +113 -57
  437. package/src/components/release-inputs.ts +69 -0
  438. package/src/components/trace-facts.test.ts +21 -0
  439. package/src/components/trace-facts.ts +27 -0
  440. package/src/compose/apply-events.test.ts +62 -0
  441. package/src/compose/apply-events.ts +100 -0
  442. package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
  443. package/src/compose/estate.test.ts +421 -0
  444. package/src/compose/estate.ts +443 -0
  445. package/src/compose/hcl.ts +311 -0
  446. package/src/compose/module-address.ts +49 -0
  447. package/src/compose/root.test.ts +416 -0
  448. package/src/compose/root.ts +592 -0
  449. package/src/compose/run.test.ts +440 -0
  450. package/src/compose/run.tofu.test.ts +176 -0
  451. package/src/compose/run.ts +527 -0
  452. package/src/compose/slice.test.ts +184 -0
  453. package/src/compose/slice.ts +290 -0
  454. package/src/compose/state-address.ts +157 -0
  455. package/src/compose/state-merge.test.ts +131 -0
  456. package/src/compose/state-merge.ts +148 -0
  457. package/src/compose.test.ts +248 -0
  458. package/src/compose.ts +627 -0
  459. package/src/composites/terraform-apply-op.test.ts +9 -0
  460. package/src/composites/terraform-apply-op.ts +7 -3
  461. package/src/cone.ts +138 -0
  462. package/src/config-file-listers.test.ts +265 -0
  463. package/src/config.ts +44 -9
  464. package/src/cost-hold.ts +29 -0
  465. package/src/describe-resources.engine-facts.test.ts +411 -0
  466. package/src/describe-resources.live-module-keyed.test.ts +290 -0
  467. package/src/describe-resources.ts +416 -70
  468. package/src/drift-read.ts +284 -0
  469. package/src/drift-record.test.ts +223 -0
  470. package/src/drift-record.ts +455 -0
  471. package/src/drift-rows.test.ts +135 -0
  472. package/src/drift-rows.ts +137 -0
  473. package/src/exit-codes.test.ts +23 -0
  474. package/src/exit-codes.ts +59 -0
  475. package/src/hcl/config-blocks.test.ts +174 -0
  476. package/src/hcl/edges.ts +21 -1
  477. package/src/hcl/files.ts +234 -0
  478. package/src/hcl/instance-forms.test.ts +94 -0
  479. package/src/hcl/instances.test.ts +137 -0
  480. package/src/hcl/instances.ts +422 -0
  481. package/src/hcl/parse.test.ts +139 -1
  482. package/src/hcl/parse.ts +149 -16
  483. package/src/hcl/roots.ts +6 -0
  484. package/src/hcl/suppressions.ts +8 -2
  485. package/src/index.ts +79 -0
  486. package/src/lint/audit-catalog.ts +29 -3
  487. package/src/lint/audit-lineage.ts +14 -2
  488. package/src/lint/post-synth/fixtures/TF024/live-with-record-store/main.tf +18 -0
  489. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/estate.chdf.hcl +1 -0
  490. package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/main.tf +10 -0
  491. package/src/lint/post-synth/fixtures/TF046/azurerm-no-resource-group/main.tf +15 -0
  492. package/src/lint/post-synth/fixtures/TF046/backend-config-only/main.tf +14 -0
  493. package/src/lint/post-synth/fixtures/TF046/derived-s3/backend.tf +7 -0
  494. package/src/lint/post-synth/fixtures/TF046/derived-s3/main.tf +9 -0
  495. package/src/lint/post-synth/fixtures/TF046/explicit-local/main.tf +16 -0
  496. package/src/lint/post-synth/fixtures/TF046/explicit-s3-beside-local-backend/main.tf +15 -0
  497. package/src/lint/post-synth/fixtures/TF046/implied-local/main.tf +9 -0
  498. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/estate.chdf.hcl +1 -0
  499. package/src/lint/post-synth/fixtures/TF046/sidecar-implied/main.tf +5 -0
  500. package/src/lint/post-synth/fixtures/TF047/clear/main.tf +12 -0
  501. package/src/lint/post-synth/fixtures/TF047/refused/main.tf +12 -0
  502. package/src/lint/post-synth/fixtures/TF047/unknown-module/main.tf +12 -0
  503. package/src/lint/post-synth/fixtures/TF047/unknown-version/main.tf +12 -0
  504. package/src/lint/post-synth/index.ts +4 -0
  505. package/src/lint/post-synth/record-store.ts +122 -0
  506. package/src/lint/post-synth/tf001.ts +2 -2
  507. package/src/lint/post-synth/tf024.test.ts +26 -5
  508. package/src/lint/post-synth/tf024.ts +105 -22
  509. package/src/lint/post-synth/tf046.test.ts +137 -0
  510. package/src/lint/post-synth/tf046.ts +204 -0
  511. package/src/lint/post-synth/tf047.test.ts +56 -0
  512. package/src/lint/post-synth/tf047.ts +114 -0
  513. package/src/live-plan-reasons.ts +54 -0
  514. package/src/lock-rows.test.ts +70 -0
  515. package/src/lock-rows.ts +82 -0
  516. package/src/lock-timeout.ts +34 -0
  517. package/src/lsp/completions.test.ts +1 -1
  518. package/src/lsp/option-keys.ts +2 -0
  519. package/src/module-catalog/index.test.ts +93 -0
  520. package/src/module-catalog/index.ts +159 -0
  521. package/src/module-catalog/refresh-cli.ts +68 -0
  522. package/src/module-catalog/static-tier.ts +1659 -0
  523. package/src/module-catalog/types.ts +88 -0
  524. package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
  525. package/src/op/activities/index.ts +9 -0
  526. package/src/op/activities/live-detect.ts +55 -26
  527. package/src/op/activities/pin-rollout.ts +3 -0
  528. package/src/op/activities/registry.test.ts +2 -1
  529. package/src/op/activities/terraform.test.ts +55 -0
  530. package/src/op/activities/terraform.ts +312 -27
  531. package/src/op/activity-contracts.test.ts +1 -0
  532. package/src/op/activity-contracts.ts +24 -1
  533. package/src/op/builders.ts +14 -0
  534. package/src/op-wave-step.test.ts +109 -0
  535. package/src/pin/__fixtures__/consumers/live/dev/main.tf +4 -0
  536. package/src/pin/__fixtures__/consumers/live/prod/main.tf +21 -0
  537. package/src/pin/__fixtures__/consumers/units/vpc/terragrunt.hcl +7 -0
  538. package/src/pin/bundle.test.ts +2 -0
  539. package/src/pin/consumers.test.ts +57 -0
  540. package/src/pin/consumers.ts +95 -0
  541. package/src/pin/edit.ts +30 -0
  542. package/src/pin/rollout.ts +16 -11
  543. package/src/plan-shape.test.ts +148 -0
  544. package/src/plan-shape.ts +128 -0
  545. package/src/plugin.ts +39 -19
  546. package/src/registry/__fixtures__/docs/documented/.terraform-docs.yml +3 -0
  547. package/src/registry/__fixtures__/docs/documented/README.md +1 -0
  548. package/src/registry/__fixtures__/docs/documented/main.tf +8 -0
  549. package/src/registry/__fixtures__/docs/json/main.tf.json +14 -0
  550. package/src/registry/__fixtures__/docs/vpc/README.md +3 -0
  551. package/src/registry/__fixtures__/docs/vpc/examples/complete/main.tf +5 -0
  552. package/src/registry/__fixtures__/docs/vpc/examples/simple/README.md +1 -0
  553. package/src/registry/__fixtures__/docs/vpc/examples/simple/main.tf +4 -0
  554. package/src/registry/__fixtures__/docs/vpc/main.tf +14 -0
  555. package/src/registry/__fixtures__/docs/vpc/outputs.tf +9 -0
  556. package/src/registry/__fixtures__/docs/vpc/variables.tf +16 -0
  557. package/src/registry/__fixtures__/proxied-root/main.tf +12 -0
  558. package/src/registry/address.ts +100 -0
  559. package/src/registry/catalog-read.ts +56 -0
  560. package/src/registry/deprecated.test.ts +139 -0
  561. package/src/registry/deprecated.ts +76 -0
  562. package/src/registry/docs.test.ts +140 -0
  563. package/src/registry/docs.ts +299 -0
  564. package/src/registry/edge/bundle.test.ts +50 -0
  565. package/src/registry/edge/edge.test.ts +176 -0
  566. package/src/registry/edge/edge.ts +211 -0
  567. package/src/registry/edge/index.ts +10 -0
  568. package/src/registry/edge/lambda-edge.ts +127 -0
  569. package/src/registry/edge/sigv4-web.ts +79 -0
  570. package/src/registry/edge/token-source.test.ts +258 -0
  571. package/src/registry/edge/token-source.ts +169 -0
  572. package/src/registry/edge/worker.ts +61 -0
  573. package/src/registry/handler.ts +89 -0
  574. package/src/registry/headers-file.ts +61 -0
  575. package/src/registry/index.ts +31 -0
  576. package/src/registry/mirror-paths.ts +10 -0
  577. package/src/registry/mirror.test.ts +198 -0
  578. package/src/registry/mirror.ts +395 -0
  579. package/src/registry/proxies.test.ts +153 -0
  580. package/src/registry/proxies.ts +132 -0
  581. package/src/registry/reader.ts +225 -0
  582. package/src/registry/registry.test.ts +262 -0
  583. package/src/registry/semver.ts +34 -0
  584. package/src/registry/serve.ts +82 -0
  585. package/src/registry/source-forms.test.ts +87 -0
  586. package/src/registry/source-forms.ts +216 -0
  587. package/src/registry/targets/artifactory.test.ts +248 -0
  588. package/src/registry/targets/artifactory.ts +257 -0
  589. package/src/registry/targets/fake-artifactory.ts +158 -0
  590. package/src/registry/targets/fake-gitlab.ts +161 -0
  591. package/src/registry/targets/fake-hcp.ts +233 -0
  592. package/src/registry/targets/fake-http.ts +97 -0
  593. package/src/registry/targets/fake-nexus.ts +152 -0
  594. package/src/registry/targets/gitlab.test.ts +208 -0
  595. package/src/registry/targets/gitlab.ts +223 -0
  596. package/src/registry/targets/hcp.test.ts +230 -0
  597. package/src/registry/targets/hcp.ts +360 -0
  598. package/src/registry/targets/hosted.ts +117 -0
  599. package/src/registry/targets/index.ts +17 -0
  600. package/src/registry/targets/module-target.ts +131 -0
  601. package/src/registry/targets/nexus.test.ts +220 -0
  602. package/src/registry/targets/nexus.ts +314 -0
  603. package/src/registry/targets/zip.test.ts +65 -0
  604. package/src/registry/targets/zip.ts +287 -0
  605. package/src/registry/writer.ts +155 -0
  606. package/src/report.test.ts +82 -1
  607. package/src/report.ts +31 -54
  608. package/src/signal-only-root.test.ts +72 -0
  609. package/src/skills/chant-terraform.md +1 -1
  610. package/src/state/azure-blob.ts +8 -0
  611. package/src/state/backend-verdict.test.ts +187 -0
  612. package/src/state/backend.test.ts +127 -0
  613. package/src/state/backend.ts +488 -0
  614. package/src/state/exec.ts +111 -0
  615. package/src/state/gcs.ts +7 -0
  616. package/src/state/index.ts +15 -0
  617. package/src/state/locks.test.ts +84 -0
  618. package/src/state/locks.ts +201 -0
  619. package/src/state/object-store.ts +7 -0
  620. package/src/state/root-states-floci.e2e.test.ts +66 -0
  621. package/src/state/root-states.test.ts +462 -0
  622. package/src/state/root-states.ts +309 -0
  623. package/src/state/s3.ts +21 -0
  624. package/src/state/store.ts +225 -0
  625. package/src/state-ops.test.ts +182 -0
  626. package/src/state-ops.ts +462 -0
  627. package/src/state-reads.ts +116 -0
  628. package/src/terragrunt/affected.ts +2 -1
  629. package/src/used-sources.test.ts +75 -0
  630. package/src/used-sources.ts +204 -0
  631. package/src/workspace-kinds.test.ts +4 -17
  632. package/workspace-kinds.json +1 -24
  633. package/dist/workspace-member.d.ts +0 -30
  634. package/dist/workspace-member.d.ts.map +0 -1
  635. package/dist/workspace-member.js +0 -91
  636. package/schemas/pulumi.schema.json +0 -34
  637. package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
  638. package/src/testdata/pulumi-member/stack-export.json +0 -53
  639. package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
  640. package/src/workspace-member.test.ts +0 -89
  641. package/src/workspace-member.ts +0 -126
@@ -0,0 +1,309 @@
1
+ /**
2
+ * The states of a set of roots, held for one run: take every lock (in the
3
+ * order given), pull each state, write each state back with retries, and
4
+ * release each lock.
5
+ *
6
+ * A state whose write still fails after the retries is kept beside the state
7
+ * in its own backend as `<key>.chant-unwritten`, and its lock stays held,
8
+ * naming the run. The next take of that lock finds the kept state, takes
9
+ * the lock over, writes the kept state, removes it, and only then returns.
10
+ * When the backend cannot take the kept copy either, it goes under the job's
11
+ * report directory (`<reportDir>/unwritten/<root>.tfstate`), never the
12
+ * reports bucket.
13
+ *
14
+ * A take also reads a state terragucci kept as `<key>.terragucci-unwritten`
15
+ * under a terragucci composed run's lock, so a repo that moves to chant
16
+ * mid-incident keeps it. That name is read and removed, never written.
17
+ *
18
+ * Lifted from terragucci's `compose.ts` (c493e0e): `ComposedRun.plan`'s lock
19
+ * take and kept write, `writeState`, `writeWithRetries`, `keep`,
20
+ * `releaseOne` and `release`.
21
+ */
22
+ import { mkdirSync, writeFileSync } from "node:fs";
23
+ import { dirname, join } from "node:path";
24
+ import { isRefused, stateObject, type LockableState, type StateObject, type StateRefusalKind } from "./backend";
25
+ import { lastLines, pullState, runBinary, type BinaryExec, type StateFile } from "./exec";
26
+ import { takeLocks, type HeldLock, type LockOperation } from "./locks";
27
+ import type { StoreFetch } from "./object-store";
28
+ import { stateStore, type StateStore } from "./store";
29
+
30
+ /** The suffix of the object beside a root's state that holds a state a run could not write. */
31
+ export const UNWRITTEN_SUFFIX = ".chant-unwritten";
32
+ /** The suffix terragucci keeps an unwritten state under: read and removed, never written. */
33
+ export const LEGACY_UNWRITTEN_SUFFIX = ".terragucci-unwritten";
34
+ /** The operation terragucci's composed run names in its locks. */
35
+ export const LEGACY_COMPOSE_OPERATION = "terragucci compose";
36
+ /** Where a kept state goes under the job's report directory when its own backend could not take it. */
37
+ export const UNWRITTEN_DIR = "unwritten";
38
+
39
+ /** The operation a composed run's locks name. */
40
+ export const COMPOSE_LOCK: LockOperation = { operation: "chant terraform.compose", info: "a composed run holds this state while it plans, applies and writes it" };
41
+
42
+ /** How long to wait before each retry of a state's write: five retries, inside the lock timeout of 5 minutes. */
43
+ export const DEFAULT_RETRY_DELAYS = [5_000, 15_000, 30_000, 60_000, 120_000];
44
+
45
+ /** One root whose state the run holds. */
46
+ export interface RootState {
47
+ root: string;
48
+ /** The root's directory, where the binary runs. */
49
+ dir: string;
50
+ /** The root's environment for the binary. */
51
+ env: NodeJS.ProcessEnv;
52
+ /** Where its state is; read from what `init` recorded, compared with the root's code, when absent. */
53
+ object?: StateObject;
54
+ /** `terraform.roots.<name>.backendConfig`, which the comparison with what init recorded reads (chant#4478). */
55
+ backendConfig?: Readonly<Record<string, string>>;
56
+ /** `terraform.roots.<name>.backendConfigFiles`, whose keys the same comparison reads (chant#4459). */
57
+ backendConfigFiles?: readonly string[];
58
+ }
59
+
60
+ export interface RootStatesOptions {
61
+ /** The roots, in the order their locks are taken. */
62
+ roots: RootState[];
63
+ /** The binary's path. */
64
+ binary: string;
65
+ exec?: BinaryExec;
66
+ /** The operation the locks name. Default COMPOSE_LOCK. */
67
+ op?: LockOperation;
68
+ /** Names the run in the locks it takes. */
69
+ runId?: string;
70
+ /** A directory of the job's own for the state files `state push` reads. */
71
+ work: string;
72
+ /** The job's report directory, where a kept state goes when its backend cannot take it. */
73
+ reportDir: string;
74
+ retryDelays?: number[];
75
+ sleep?: (ms: number) => Promise<void>;
76
+ fetch?: StoreFetch;
77
+ /** Opens a root's store; a test's fake stands in here. */
78
+ openStore?: (root: RootState, object: LockableState) => StateStore;
79
+ now?: () => string;
80
+ /** Who the lock info names. */
81
+ who?: string;
82
+ log?: (line: string) => void;
83
+ }
84
+
85
+ /** Why a root's state is not locked. */
86
+ export interface RootRefusal {
87
+ root: string;
88
+ backend: string;
89
+ refused: StateRefusalKind;
90
+ reason: string;
91
+ }
92
+
93
+ /** The roots whose state chant does not lock: the take stops before any lock. */
94
+ export class StateRefusedError extends Error {
95
+ constructor(readonly refusals: RootRefusal[]) {
96
+ super(refusals.map((r) => `${r.root}: ${r.reason}`).join("\n"));
97
+ this.name = "StateRefusedError";
98
+ }
99
+ }
100
+
101
+ /** How a write ended: written, or kept with the lock held. */
102
+ export type WriteOutcome = { written: true; attempts: number } | { written: false; attempts: number; error: string; kept: string; lockHeld: true };
103
+
104
+ const sleepMs = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
105
+ const safeName = (root: string): string => (root === "." ? "root" : root.replace(/[^\w.-]/g, "_"));
106
+ const body = (s: StateFile): string => JSON.stringify(s, null, 2) + "\n";
107
+
108
+ export class RootStates {
109
+ private readonly exec: BinaryExec;
110
+ private readonly op: LockOperation;
111
+ private readonly objects = new Map<string, StateObject>();
112
+ private locks: HeldLock[] = [];
113
+ private readonly heldOnPurpose = new Set<string>();
114
+
115
+ constructor(private readonly o: RootStatesOptions) {
116
+ this.exec = o.exec ?? runBinary;
117
+ this.op = o.op ?? COMPOSE_LOCK;
118
+ if (o.runId) this.op = { operation: this.op.operation, info: `${this.op.info} (run ${o.runId})` };
119
+ }
120
+
121
+ private log(line: string): void {
122
+ this.o.log?.(line);
123
+ }
124
+
125
+ private rootOf(root: string): RootState {
126
+ const r = this.o.roots.find((x) => x.root === root);
127
+ if (!r) throw new Error(`${root} is not one of this run's roots`);
128
+ return r;
129
+ }
130
+
131
+ /** Where a root's state is, or why it is not locked. */
132
+ object(root: string): StateObject {
133
+ let o = this.objects.get(root);
134
+ if (!o) {
135
+ const r = this.rootOf(root);
136
+ o = r.object ?? stateObject(r.dir, r.env, undefined, this.o.binary, {
137
+ ...(r.backendConfig ? { backendConfig: r.backendConfig } : {}),
138
+ ...(r.backendConfigFiles ? { backendConfigFiles: r.backendConfigFiles } : {}),
139
+ });
140
+ this.objects.set(root, o);
141
+ }
142
+ return o;
143
+ }
144
+
145
+ /** Every root whose state chant does not lock, with why. */
146
+ refusals(): RootRefusal[] {
147
+ return this.o.roots.flatMap((r) => {
148
+ const o = this.object(r.root);
149
+ return isRefused(o) ? [{ root: r.root, backend: o.backend, refused: o.refused, reason: o.reason }] : [];
150
+ });
151
+ }
152
+
153
+ /** The locks held now, by root. */
154
+ held(): string[] {
155
+ return this.locks.map((l) => l.root);
156
+ }
157
+
158
+ private lockOf(root: string): HeldLock | undefined {
159
+ return this.locks.find((l) => l.root === root);
160
+ }
161
+
162
+ /**
163
+ * Take every root's lock, in order. A root chant does not lock stops the
164
+ * take before any lock (StateRefusedError); a lock another run holds
165
+ * releases what was taken and throws StateLockedError, naming the holder.
166
+ * A lock an earlier run of this operation left held with a kept state is
167
+ * taken over, and every kept state is written before this returns.
168
+ */
169
+ async take(): Promise<void> {
170
+ const refused = this.refusals();
171
+ if (refused.length) throw new StateRefusedError(refused);
172
+ const stores = this.o.roots.map((r) => {
173
+ const object = this.object(r.root) as LockableState;
174
+ return { root: r.root, store: this.o.openStore ? this.o.openStore(r, object) : stateStore(object, r.dir, this.o.fetch) };
175
+ });
176
+ const now = this.o.now?.() ?? new Date().toISOString();
177
+ this.locks = await takeLocks(
178
+ stores,
179
+ now,
180
+ this.op,
181
+ async (root, store, held) => {
182
+ // A lock an earlier composed run left held for a state it could not write: this run writes that state first.
183
+ const ours = held.info.includes(this.op.operation) || held.info.includes(LEGACY_COMPOSE_OPERATION);
184
+ if (!ours || !(await this.kept(store))) return false;
185
+ this.log(`${root}: its lock is held by an earlier run that kept a state it could not write; this run takes the lock over and writes that state first`);
186
+ return true;
187
+ },
188
+ this.o.who,
189
+ );
190
+ for (const l of this.locks) {
191
+ try {
192
+ await this.writeKept(l);
193
+ } catch (e) {
194
+ // The kept state is still unwritten: its lock stays held, the others are released.
195
+ this.heldOnPurpose.add(l.root);
196
+ await this.release();
197
+ throw new Error(`${l.root}: the state an earlier run kept for it could not be written: ${(e as Error).message}`);
198
+ }
199
+ }
200
+ }
201
+
202
+ /** The state kept beside a store's state and the suffix it is under, chant's name first; undefined when none is kept. */
203
+ private async kept(store: StateStore): Promise<{ suffix: string; text: string } | undefined> {
204
+ for (const suffix of [UNWRITTEN_SUFFIX, LEGACY_UNWRITTEN_SUFFIX]) {
205
+ const text = await store.readBeside(suffix).catch(() => undefined);
206
+ if (text?.trim()) return { suffix, text };
207
+ }
208
+ return undefined;
209
+ }
210
+
211
+ /** A state an earlier run kept beside a root's state: write it, then remove what was kept. */
212
+ private async writeKept(lock: HeldLock): Promise<void> {
213
+ const kept = await this.kept(lock.store);
214
+ if (!kept) return;
215
+ await this.writeState(lock.root, JSON.parse(kept.text) as StateFile);
216
+ await lock.store.removeBeside(kept.suffix);
217
+ this.log(`${lock.root}: wrote the state an earlier run kept at ${lock.store.besideLocation(kept.suffix)}`);
218
+ }
219
+
220
+ /** A root's state as `state pull` gives it, or null when it has none. */
221
+ pull(root: string): Promise<StateFile | null> {
222
+ const r = this.rootOf(root);
223
+ return pullState(this.exec, this.o.binary, root, r.dir, r.env);
224
+ }
225
+
226
+ /** Write one root's state under its lock: the store's own write (azurerm's lease), else `state push -lock=false`. */
227
+ private async writeState(root: string, state: StateFile): Promise<void> {
228
+ const lock = this.lockOf(root);
229
+ if (!lock) throw new Error(`${root}: its state is written under its lock, and none is held`);
230
+ if (lock.store.write) {
231
+ await lock.store.write(body(state));
232
+ return;
233
+ }
234
+ const r = this.rootOf(root);
235
+ const file = join(this.o.work, `${safeName(root)}.tfstate`);
236
+ mkdirSync(this.o.work, { recursive: true });
237
+ writeFileSync(file, body(state));
238
+ const push = await this.exec(this.o.binary, ["state", "push", "-lock=false", file], r.dir, r.env);
239
+ if (push.code !== 0) throw new Error(`state push failed: ${lastLines(push.out, 3)}`);
240
+ }
241
+
242
+ /**
243
+ * Write one root's state, retrying after each delay while the lock is
244
+ * held. A state still unwritten after the last retry is kept, and its lock
245
+ * stays held: `release` leaves it.
246
+ */
247
+ async write(root: string, state: StateFile): Promise<WriteOutcome> {
248
+ const delays = this.o.retryDelays ?? DEFAULT_RETRY_DELAYS;
249
+ const sleep = this.o.sleep ?? sleepMs;
250
+ let last = "";
251
+ for (let i = 0; i <= delays.length; i++) {
252
+ try {
253
+ await this.writeState(root, state);
254
+ return { written: true, attempts: i + 1 };
255
+ } catch (e) {
256
+ last = (e as Error).message;
257
+ if (i < delays.length) {
258
+ this.log(`${root}: the write of its state failed (${last}); trying again in ${Math.round(delays[i]! / 1000)}s`);
259
+ await sleep(delays[i]!);
260
+ }
261
+ }
262
+ }
263
+ const kept = await this.keep(root, state);
264
+ this.heldOnPurpose.add(root);
265
+ this.log(`${root}: its state could not be written (${last}); kept at ${kept}, and its lock stays held until a later run writes it`);
266
+ return { written: false, attempts: delays.length + 1, error: last, kept, lockHeld: true };
267
+ }
268
+
269
+ /** Keep a state that could not be written: beside it in its own backend, else as a file of the job's report directory. Returns where. */
270
+ private async keep(root: string, state: StateFile): Promise<string> {
271
+ const lock = this.lockOf(root);
272
+ try {
273
+ if (lock) {
274
+ await lock.store.writeBeside(UNWRITTEN_SUFFIX, body(state));
275
+ return lock.store.besideLocation(UNWRITTEN_SUFFIX);
276
+ }
277
+ } catch (e) {
278
+ this.log(`${root}: the state could not be kept beside its backend's state either (${(e as Error).message}); it is kept under the job's report directory`);
279
+ }
280
+ const file = join(this.o.reportDir, UNWRITTEN_DIR, `${safeName(root)}.tfstate`);
281
+ mkdirSync(dirname(file), { recursive: true });
282
+ writeFileSync(file, body(state));
283
+ return file;
284
+ }
285
+
286
+ /**
287
+ * Keep a root's lock held past {@link release}, as a kept state's is: for a
288
+ * root whose state the run could neither write nor keep, so no other run
289
+ * plans against a state that may not hold what applied.
290
+ */
291
+ hold(root: string): void {
292
+ if (this.lockOf(root)) this.heldOnPurpose.add(root);
293
+ }
294
+
295
+ /** The roots whose lock stays held past {@link release}. */
296
+ heldOnPurposeRoots(): string[] {
297
+ return [...this.heldOnPurpose].filter((r) => this.lockOf(r)).sort();
298
+ }
299
+
300
+ /** Release a root's lock, or every lock but those held on purpose for a kept state. */
301
+ async release(root?: string): Promise<void> {
302
+ const which = root ? [root] : [...new Set(this.locks.map((l) => l.root))].filter((r) => !this.heldOnPurpose.has(r));
303
+ for (const r of which) {
304
+ const mine = this.locks.filter((l) => l.root === r);
305
+ this.locks = this.locks.filter((l) => l.root !== r);
306
+ for (const l of mine) await l.release().catch((e: unknown) => this.log(`${r}: its lock could not be released: ${(e as Error).message}`));
307
+ }
308
+ }
309
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The S3 client a state's lock and write use: chant core's (#4554,
3
+ * `@intentius/chant/object-store`), which signs with chant's one AWS
4
+ * signer. Get, put, put only when absent, head and delete one object; with
5
+ * no static keys, a role is assumed with the job's OIDC token through STS
6
+ * AssumeRoleWithWebIdentity.
7
+ */
8
+ import { s3FromEnv as coreS3FromEnv, type S3Target } from "@intentius/chant/object-store";
9
+
10
+ export { assumeRoleWithWebIdentity, S3Client, sign, type S3Credentials, type S3Location, type S3Target, type S3WebIdentity } from "@intentius/chant/object-store";
11
+
12
+ /**
13
+ * The target for `bucket` from the AWS environment variables. Credentials,
14
+ * first match wins: static keys in `AWS_ACCESS_KEY_ID` and
15
+ * `AWS_SECRET_ACCESS_KEY`; `AWS_ROLE_ARN` assumed with the token in
16
+ * `AWS_WEB_IDENTITY_TOKEN_FILE`, in a session named `chant-state` unless
17
+ * AWS_ROLE_SESSION_NAME names one.
18
+ */
19
+ export function s3FromEnv(where: { bucket: string; endpoint?: string }, env: NodeJS.ProcessEnv = process.env): S3Target {
20
+ return coreS3FromEnv({ ...where, sessionName: "chant-state", setting: "the state bucket" }, env);
21
+ }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * A state where it is, opened for its lock and for what a run keeps beside
3
+ * it. Each backend locks its own way:
4
+ *
5
+ * s3 the lock file `use_lockfile` takes, `<key>.tflock`, written only
6
+ * when absent
7
+ * gcs the lock object `<prefix>/<workspace>.tflock`, written only when
8
+ * absent; the ID `force-unlock` takes is its generation
9
+ * azurerm a lease on the state blob, with the lock info base64 in its
10
+ * `terraformlockid` metadata; the binary cannot write the blob
11
+ * while chant holds the lease, so the store writes it
12
+ * local a lock file `<path>.tflock` beside the state, created only when
13
+ * absent. It keeps another chant run out; the binary's own local
14
+ * lock is a file lock it takes per command, so the run writes
15
+ * with `-lock=false` while it holds this one. A bare
16
+ * `terraform apply` on the same checkout at the same time is not
17
+ * kept out. Ruled on #4481 (ruling 4, 2026-10-25): chant's own
18
+ * lock file, no Terraform lock-info file, local backends allowed.
19
+ *
20
+ * Lifted from terragucci's `backend.ts` (c493e0e): `StateStore`, `s3Store`,
21
+ * `gcsStore` and `azureStore`, plus a local store and `removeBeside`.
22
+ */
23
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
24
+ import { dirname, isAbsolute, join } from "node:path";
25
+ import { AzureBlobClient, type AzureTarget } from "./azure-blob";
26
+ import type { AzureState, GcsState, LocalState, LockableState, S3State } from "./backend";
27
+ import { GcsClient } from "./gcs";
28
+ import { StoreError, type StoreFetch } from "./object-store";
29
+ import { S3Client } from "./s3";
30
+
31
+ /** The lock a state's backend holds: the ID `force-unlock` takes, and the lock info the binary (or a run) wrote. */
32
+ export interface LockHolder {
33
+ id: string;
34
+ /** The lock info as written, JSON. */
35
+ info: string;
36
+ }
37
+
38
+ /** A state where it is, for what locks it and keeps beside it. */
39
+ export interface StateStore {
40
+ readonly backend: LockableState["backend"];
41
+ /** Where the state is. */
42
+ readonly location: string;
43
+ /** Where its lock is: the lock object, or the blob whose lease is the lock. */
44
+ readonly lockLocation: string;
45
+ /** The lock the backend holds now, or undefined. */
46
+ heldLock(): Promise<LockHolder | undefined>;
47
+ /** Take the state's lock with `info`, the way the backend takes it: false when it is held. */
48
+ lock(info: string): Promise<boolean>;
49
+ /** Release the lock `lock` took, or the one `adopt` took over. */
50
+ unlock(): Promise<void>;
51
+ /**
52
+ * Write a new state under the lock, for a backend whose lock keeps the
53
+ * binary from writing (azurerm's lease); undefined for a backend the
54
+ * binary writes with `-lock=false` while the run holds the lock.
55
+ */
56
+ write?: (body: string) => Promise<void>;
57
+ /**
58
+ * The object beside the state whose key is the state's with `suffix` after
59
+ * it, in the same bucket, container or directory, under the state's own
60
+ * access controls: read (undefined when there is none), written, removed.
61
+ */
62
+ readBeside(suffix: string): Promise<string | undefined>;
63
+ writeBeside(suffix: string, body: string): Promise<void>;
64
+ removeBeside(suffix: string): Promise<void>;
65
+ /** Where the object beside the state with `suffix` is, for messages. */
66
+ besideLocation(suffix: string): string;
67
+ /**
68
+ * Take over a lock this store finds held, which an earlier run of the same
69
+ * operation left held on purpose: `unlock` then releases it. A lease
70
+ * (azurerm) is taken over by its ID, the others need nothing.
71
+ */
72
+ adopt(held: LockHolder): void;
73
+ }
74
+
75
+ /** The ID in a lock info's JSON, or "" when it names none. */
76
+ export function lockId(text: string): string {
77
+ try {
78
+ const v = JSON.parse(text) as { ID?: unknown };
79
+ return typeof v.ID === "string" ? v.ID : "";
80
+ } catch {
81
+ return "";
82
+ }
83
+ }
84
+
85
+ const JSON_TYPE = "application/json";
86
+
87
+ /** The S3 state, locked by the lock file `use_lockfile` takes. */
88
+ function s3Store(o: S3State, fetchFn?: StoreFetch): StateStore {
89
+ const client = new S3Client(o.target, fetchFn);
90
+ const lockKey = `${o.key}.tflock`;
91
+ return {
92
+ backend: "s3",
93
+ location: `s3://${o.bucket}/${o.key}`,
94
+ lockLocation: `s3://${o.bucket}/${lockKey}`,
95
+ heldLock: async () => {
96
+ const text = await client.get(lockKey);
97
+ return text === undefined ? undefined : { id: lockId(text), info: text };
98
+ },
99
+ lock: (info) => client.putIfAbsent(lockKey, info, JSON_TYPE),
100
+ unlock: () => client.remove(lockKey),
101
+ readBeside: (suffix) => client.get(`${o.key}${suffix}`),
102
+ writeBeside: async (suffix, body) => void (await client.put(`${o.key}${suffix}`, body, JSON_TYPE)),
103
+ removeBeside: (suffix) => client.remove(`${o.key}${suffix}`),
104
+ besideLocation: (suffix) => `s3://${o.bucket}/${o.key}${suffix}`,
105
+ adopt: () => {},
106
+ };
107
+ }
108
+
109
+ /** The GCS state, locked by `<prefix>/<workspace>.tflock`, whose generation is the lock's ID. */
110
+ function gcsStore(o: GcsState, fetchFn?: StoreFetch): StateStore {
111
+ const client = new GcsClient(o.target, fetchFn);
112
+ return {
113
+ backend: "gcs",
114
+ location: `gs://${o.bucket}/${o.key}`,
115
+ lockLocation: `gs://${o.bucket}/${o.lock}`,
116
+ heldLock: async () => {
117
+ const head = await client.head(o.lock);
118
+ const info = head.exists ? await client.get(o.lock) : undefined;
119
+ if (!head.exists || info === undefined) return undefined;
120
+ return { id: head.generation ?? lockId(info), info };
121
+ },
122
+ lock: (info) => client.putIfAbsent(o.lock, info, JSON_TYPE),
123
+ unlock: () => client.remove(o.lock),
124
+ readBeside: (suffix) => client.get(`${o.key}${suffix}`),
125
+ writeBeside: async (suffix, body) => void (await client.put(`${o.key}${suffix}`, body, JSON_TYPE)),
126
+ removeBeside: (suffix) => client.remove(`${o.key}${suffix}`),
127
+ besideLocation: (suffix) => `gs://${o.bucket}/${o.key}${suffix}`,
128
+ adopt: () => {},
129
+ };
130
+ }
131
+
132
+ /** The metadata an azurerm backend keeps a lock's info in, base64 JSON, on the leased blob. */
133
+ export const LOCK_METADATA = "terraformlockid";
134
+
135
+ /** The azurerm state blob, locked by a lease on it with the lock info in its metadata. */
136
+ function azureStore(o: AzureState, fetchFn?: StoreFetch): StateStore {
137
+ const client = new AzureBlobClient({ account: o.account, container: o.container, endpoint: o.endpoint, ...o.auth } as AzureTarget, fetchFn);
138
+ let leaseId: string | undefined;
139
+ const at = `az://${o.account}/${o.container}/${o.key}`;
140
+ return {
141
+ backend: "azurerm",
142
+ location: at,
143
+ lockLocation: at,
144
+ heldLock: async () => {
145
+ const head = await client.head(o.key);
146
+ if (!head.exists || head.leaseState !== "leased") return undefined;
147
+ const meta = head.metadata[LOCK_METADATA];
148
+ const info = meta ? Buffer.from(meta, "base64").toString("utf-8") : "";
149
+ return { id: lockId(info), info };
150
+ },
151
+ lock: async (info) => {
152
+ const id = lockId(info);
153
+ // The backend leases a blob that is there: a state with none yet gets an empty one first, as the backend makes it.
154
+ if (!(await client.head(o.key)).exists) await client.putBlob(o.key, "", JSON_TYPE, { ifNoneMatch: true });
155
+ if (!(await client.lease(o.key, "acquire", id))) return false;
156
+ leaseId = id;
157
+ await client.setMetadata(o.key, { [LOCK_METADATA]: Buffer.from(info).toString("base64") }, id);
158
+ return true;
159
+ },
160
+ unlock: async () => {
161
+ if (!leaseId) return;
162
+ const id = leaseId;
163
+ await client.setMetadata(o.key, {}, id);
164
+ await client.lease(o.key, "release", id);
165
+ leaseId = undefined;
166
+ },
167
+ write: async (body) => {
168
+ if (!leaseId) throw new StoreError(`${at} is written under its lease, and none is held`);
169
+ // As the backend writes it: a snapshot first when it takes snapshots, and the lock info kept on the blob.
170
+ if (o.snapshot && (await client.head(o.key)).exists) await client.snapshot(o.key);
171
+ const meta = (await client.head(o.key)).metadata;
172
+ await client.putBlob(o.key, body, JSON_TYPE, { leaseId, metadata: meta });
173
+ },
174
+ readBeside: (suffix) => client.get(`${o.key}${suffix}`),
175
+ writeBeside: async (suffix, body) => void (await client.putBlob(`${o.key}${suffix}`, body, JSON_TYPE)),
176
+ removeBeside: (suffix) => client.remove(`${o.key}${suffix}`),
177
+ besideLocation: (suffix) => `${at}${suffix}`,
178
+ adopt: (held) => {
179
+ leaseId = held.id;
180
+ },
181
+ };
182
+ }
183
+
184
+ /** A local state, locked by `<path>.tflock` created only when absent. `dir` is the root the path is relative to. */
185
+ function localStore(o: LocalState, dir: string): StateStore {
186
+ const file = isAbsolute(o.path) ? o.path : join(dir, o.path);
187
+ const lockFile = `${file}.tflock`;
188
+ const read = (f: string): string | undefined => (existsSync(f) ? readFileSync(f, "utf-8") : undefined);
189
+ return {
190
+ backend: "local",
191
+ location: file,
192
+ lockLocation: lockFile,
193
+ heldLock: async () => {
194
+ const text = read(lockFile);
195
+ return text === undefined ? undefined : { id: lockId(text), info: text };
196
+ },
197
+ lock: async (info) => {
198
+ mkdirSync(dirname(lockFile), { recursive: true });
199
+ try {
200
+ writeFileSync(lockFile, info, { flag: "wx" });
201
+ return true;
202
+ } catch (e) {
203
+ if ((e as NodeJS.ErrnoException).code === "EEXIST") return false;
204
+ throw e;
205
+ }
206
+ },
207
+ unlock: async () => rmSync(lockFile, { force: true }),
208
+ readBeside: async (suffix) => read(`${file}${suffix}`),
209
+ writeBeside: async (suffix, body) => {
210
+ mkdirSync(dirname(file), { recursive: true });
211
+ writeFileSync(`${file}${suffix}`, body);
212
+ },
213
+ removeBeside: async (suffix) => rmSync(`${file}${suffix}`, { force: true }),
214
+ besideLocation: (suffix) => `${file}${suffix}`,
215
+ adopt: () => {},
216
+ };
217
+ }
218
+
219
+ /** The store a state is in, opened with the credentials stateObject chose. `dir` is the root a local path is relative to. */
220
+ export function stateStore(o: LockableState, dir: string, fetchFn?: StoreFetch): StateStore {
221
+ if (o.backend === "s3") return s3Store(o, fetchFn);
222
+ if (o.backend === "gcs") return gcsStore(o, fetchFn);
223
+ if (o.backend === "azurerm") return azureStore(o, fetchFn);
224
+ return localStore(o, dir);
225
+ }