@intentius/chant 0.124.46 → 0.124.49

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 (726) hide show
  1. package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  2. package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  3. package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  4. package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  6. package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  7. package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
  8. package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
  9. package/dist/__golden__/wave-loop/op-denied.json +16 -0
  10. package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  11. package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  12. package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
  13. package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
  14. package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
  15. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  16. package/dist/__golden__/wave-loop/op-held-on.json +16 -0
  17. package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  18. package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  19. package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  20. package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  21. package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
  22. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  23. package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
  24. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  25. package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
  26. package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
  27. package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  28. package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  29. package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
  30. package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  31. package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  32. package/dist/__golden__/wave-loop/op-overridden.json +16 -0
  33. package/dist/__golden__/wave-loop/op-per-run.json +20 -0
  34. package/dist/__golden__/wave-loop/op-point.json +13 -0
  35. package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
  36. package/dist/__golden__/wave-loop/op-sealed.json +10 -0
  37. package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
  38. package/dist/__golden__/wave-loop/op-spend.json +10 -0
  39. package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
  40. package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
  41. package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
  42. package/dist/__golden__/wave-loop/op-wide.json +39 -0
  43. package/dist/action-pins.d.ts +61 -0
  44. package/dist/action-pins.d.ts.map +1 -0
  45. package/dist/action-pins.js +77 -0
  46. package/dist/cli/commands/carve.d.ts +3 -0
  47. package/dist/cli/commands/carve.d.ts.map +1 -1
  48. package/dist/cli/commands/carve.js +36 -7
  49. package/dist/cli/commands/import-report.d.ts +26 -0
  50. package/dist/cli/commands/import-report.d.ts.map +1 -0
  51. package/dist/cli/commands/import-report.js +65 -0
  52. package/dist/cli/commands/import.d.ts +11 -1
  53. package/dist/cli/commands/import.d.ts.map +1 -1
  54. package/dist/cli/commands/import.js +22 -3
  55. package/dist/cli/commands/migrate.d.ts +13 -1
  56. package/dist/cli/commands/migrate.d.ts.map +1 -1
  57. package/dist/cli/commands/migrate.js +134 -10
  58. package/dist/cli/handlers/approve.d.ts.map +1 -1
  59. package/dist/cli/handlers/approve.js +41 -14
  60. package/dist/cli/handlers/build.d.ts.map +1 -1
  61. package/dist/cli/handlers/build.js +6 -1
  62. package/dist/cli/handlers/graph-reader.d.ts +26 -0
  63. package/dist/cli/handlers/graph-reader.d.ts.map +1 -0
  64. package/dist/cli/handlers/graph-reader.js +41 -0
  65. package/dist/cli/handlers/graph.d.ts.map +1 -1
  66. package/dist/cli/handlers/graph.js +14 -0
  67. package/dist/cli/handlers/ledger.d.ts +3 -0
  68. package/dist/cli/handlers/ledger.d.ts.map +1 -0
  69. package/dist/cli/handlers/ledger.js +55 -0
  70. package/dist/cli/handlers/migrate.d.ts.map +1 -1
  71. package/dist/cli/handlers/migrate.js +18 -11
  72. package/dist/cli/handlers/misc.d.ts.map +1 -1
  73. package/dist/cli/handlers/misc.js +12 -1
  74. package/dist/cli/handlers/pr-env.js +2 -2
  75. package/dist/cli/handlers/pr.d.ts +15 -1
  76. package/dist/cli/handlers/pr.d.ts.map +1 -1
  77. package/dist/cli/handlers/pr.js +183 -40
  78. package/dist/cli/handlers/query.d.ts +7 -2
  79. package/dist/cli/handlers/query.d.ts.map +1 -1
  80. package/dist/cli/handlers/query.js +16 -4
  81. package/dist/cli/handlers/release.d.ts.map +1 -1
  82. package/dist/cli/handlers/release.js +6 -3
  83. package/dist/cli/handlers/search.d.ts +2 -4
  84. package/dist/cli/handlers/search.d.ts.map +1 -1
  85. package/dist/cli/handlers/search.js +13 -39
  86. package/dist/cli/main.d.ts.map +1 -1
  87. package/dist/cli/main.js +87 -14
  88. package/dist/cli/parse-args.d.ts.map +1 -1
  89. package/dist/cli/parse-args.js +47 -2
  90. package/dist/cli/registry.d.ts +22 -4
  91. package/dist/cli/registry.d.ts.map +1 -1
  92. package/dist/components/builders.d.ts +5 -1
  93. package/dist/components/builders.d.ts.map +1 -1
  94. package/dist/components/builders.js +5 -0
  95. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  96. package/dist/components/capability-plugin-loader.js +16 -1
  97. package/dist/components/capability-plugin.d.ts +14 -0
  98. package/dist/components/capability-plugin.d.ts.map +1 -1
  99. package/dist/components/pr-hooks.d.ts +21 -1
  100. package/dist/components/pr-hooks.d.ts.map +1 -1
  101. package/dist/components/pr-hooks.js +72 -4
  102. package/dist/components/pr-pipeline.d.ts +45 -10
  103. package/dist/components/pr-pipeline.d.ts.map +1 -1
  104. package/dist/components/pr-pipeline.js +47 -14
  105. package/dist/components/pr-policy.d.ts +36 -0
  106. package/dist/components/pr-policy.d.ts.map +1 -1
  107. package/dist/components/pr-policy.js +36 -0
  108. package/dist/components/pr-run.d.ts +3 -0
  109. package/dist/components/pr-run.d.ts.map +1 -1
  110. package/dist/components/pr-run.js +1 -0
  111. package/dist/components/starter-plugin.d.ts +1 -0
  112. package/dist/components/starter-plugin.d.ts.map +1 -1
  113. package/dist/components/starter-plugin.js +5 -1
  114. package/dist/components/verbs/index.d.ts +2 -0
  115. package/dist/components/verbs/index.d.ts.map +1 -1
  116. package/dist/components/verbs/index.js +2 -0
  117. package/dist/components/verbs/publish-artifact.d.ts +65 -0
  118. package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
  119. package/dist/components/verbs/publish-artifact.js +74 -0
  120. package/dist/components/verbs/publish-image.d.ts +129 -0
  121. package/dist/components/verbs/publish-image.d.ts.map +1 -0
  122. package/dist/components/verbs/publish-image.js +145 -0
  123. package/dist/config-environments.d.ts +20 -0
  124. package/dist/config-environments.d.ts.map +1 -1
  125. package/dist/config-environments.js +12 -0
  126. package/dist/config.d.ts +25 -1
  127. package/dist/config.d.ts.map +1 -1
  128. package/dist/config.js +10 -2
  129. package/dist/gate-decision.d.ts +20 -2
  130. package/dist/gate-decision.d.ts.map +1 -1
  131. package/dist/gate-decision.js +30 -3
  132. package/dist/graph-ir.d.ts +25 -0
  133. package/dist/graph-ir.d.ts.map +1 -1
  134. package/dist/graph-walk.d.ts +66 -0
  135. package/dist/graph-walk.d.ts.map +1 -0
  136. package/dist/graph-walk.js +70 -0
  137. package/dist/import/parser.d.ts +19 -0
  138. package/dist/import/parser.d.ts.map +1 -1
  139. package/dist/lexicon.d.ts +94 -6
  140. package/dist/lexicon.d.ts.map +1 -1
  141. package/dist/lifecycle/gate-ledger.d.ts +11 -0
  142. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  143. package/dist/lifecycle/git.d.ts +2 -5
  144. package/dist/lifecycle/git.d.ts.map +1 -1
  145. package/dist/lifecycle/ledger-read.d.ts +65 -0
  146. package/dist/lifecycle/ledger-read.d.ts.map +1 -0
  147. package/dist/lifecycle/ledger-read.js +160 -0
  148. package/dist/lifecycle/release-workstation.d.ts +7 -0
  149. package/dist/lifecycle/release-workstation.d.ts.map +1 -1
  150. package/dist/lifecycle/release-workstation.js +25 -0
  151. package/dist/lifecycle/run-ledger.d.ts +4 -3
  152. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  153. package/dist/lifecycle/run-ledger.js +1 -0
  154. package/dist/lint/discover.js +3 -1
  155. package/dist/lint/post-synth.d.ts +127 -1
  156. package/dist/lint/post-synth.d.ts.map +1 -1
  157. package/dist/lint/post-synth.js +84 -2
  158. package/dist/lint/rules/op/index.d.ts +1 -0
  159. package/dist/lint/rules/op/index.d.ts.map +1 -1
  160. package/dist/lint/rules/op/index.js +2 -0
  161. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
  162. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
  163. package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
  164. package/dist/op/activities/activity-contracts.d.ts +9 -6
  165. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  166. package/dist/op/activities/activity-contracts.js +5 -2
  167. package/dist/op/activities/index.d.ts +2 -2
  168. package/dist/op/activities/index.d.ts.map +1 -1
  169. package/dist/op/activities/index.js +1 -1
  170. package/dist/op/activities/review-agent.d.ts +90 -4
  171. package/dist/op/activities/review-agent.d.ts.map +1 -1
  172. package/dist/op/activities/review-agent.js +217 -19
  173. package/dist/op/activities/work-evidence.d.ts +8 -1
  174. package/dist/op/activities/work-evidence.d.ts.map +1 -1
  175. package/dist/op/activities/work-evidence.js +1 -0
  176. package/dist/op/builders.d.ts +9 -5
  177. package/dist/op/builders.d.ts.map +1 -1
  178. package/dist/op/builders.js +9 -5
  179. package/dist/op/composites/apply-op.d.ts +29 -2
  180. package/dist/op/composites/apply-op.d.ts.map +1 -1
  181. package/dist/op/composites/apply-op.js +20 -1
  182. package/dist/op/gate-approval-base.d.ts +25 -0
  183. package/dist/op/gate-approval-base.d.ts.map +1 -0
  184. package/dist/op/gate-approval-base.js +71 -0
  185. package/dist/op/gate-approval.d.ts +18 -9
  186. package/dist/op/gate-approval.d.ts.map +1 -1
  187. package/dist/op/gate-approval.js +13 -31
  188. package/dist/op/gate-name.d.ts +13 -0
  189. package/dist/op/gate-name.d.ts.map +1 -1
  190. package/dist/op/gate-name.js +14 -1
  191. package/dist/op/gate-quorum.d.ts +100 -3
  192. package/dist/op/gate-quorum.d.ts.map +1 -1
  193. package/dist/op/gate-quorum.js +108 -6
  194. package/dist/op/gate-review.d.ts +9 -0
  195. package/dist/op/gate-review.d.ts.map +1 -1
  196. package/dist/op/gate-review.js +13 -0
  197. package/dist/op/gate.d.ts +47 -5
  198. package/dist/op/gate.d.ts.map +1 -1
  199. package/dist/op/gate.js +25 -4
  200. package/dist/op/index.d.ts +3 -3
  201. package/dist/op/index.d.ts.map +1 -1
  202. package/dist/op/index.js +1 -1
  203. package/dist/op/local-executor.d.ts +45 -3
  204. package/dist/op/local-executor.d.ts.map +1 -1
  205. package/dist/op/local-executor.js +68 -7
  206. package/dist/op/op-waves-run.d.ts +26 -1
  207. package/dist/op/op-waves-run.d.ts.map +1 -1
  208. package/dist/op/op-waves-run.js +259 -13
  209. package/dist/op/op-waves.d.ts +72 -5
  210. package/dist/op/op-waves.d.ts.map +1 -1
  211. package/dist/op/op-waves.js +110 -15
  212. package/dist/op/runtime.d.ts +11 -0
  213. package/dist/op/runtime.d.ts.map +1 -1
  214. package/dist/op/schedule-overdue.d.ts +16 -0
  215. package/dist/op/schedule-overdue.d.ts.map +1 -1
  216. package/dist/op/schedule-overdue.js +36 -0
  217. package/dist/op/stage-hooks.d.ts +9 -2
  218. package/dist/op/stage-hooks.d.ts.map +1 -1
  219. package/dist/op/stage-hooks.js +7 -5
  220. package/dist/op/workspace-run-env.d.ts +22 -0
  221. package/dist/op/workspace-run-env.d.ts.map +1 -0
  222. package/dist/op/workspace-run-env.js +21 -0
  223. package/dist/pr-forge.d.ts +24 -0
  224. package/dist/pr-forge.d.ts.map +1 -1
  225. package/dist/pr-forge.js +47 -0
  226. package/dist/pr-loop.d.ts +10 -0
  227. package/dist/pr-loop.d.ts.map +1 -1
  228. package/dist/pr-loop.js +4 -0
  229. package/dist/pr-migrations.d.ts +62 -0
  230. package/dist/pr-migrations.d.ts.map +1 -0
  231. package/dist/pr-migrations.js +50 -0
  232. package/dist/pr-post.d.ts +42 -0
  233. package/dist/pr-post.d.ts.map +1 -1
  234. package/dist/pr-post.js +68 -2
  235. package/dist/read-filter.d.ts +65 -0
  236. package/dist/read-filter.d.ts.map +1 -0
  237. package/dist/read-filter.js +179 -0
  238. package/dist/run-event.d.ts +1 -1
  239. package/dist/run-event.d.ts.map +1 -1
  240. package/dist/terraform/carve.d.ts +24 -3
  241. package/dist/terraform/carve.d.ts.map +1 -1
  242. package/dist/terraform/carve.js +32 -14
  243. package/dist/terraform/cuts.d.ts +97 -0
  244. package/dist/terraform/cuts.d.ts.map +1 -0
  245. package/dist/terraform/cuts.js +138 -0
  246. package/dist/terraform/folds.d.ts +26 -0
  247. package/dist/terraform/folds.d.ts.map +1 -0
  248. package/dist/terraform/folds.js +38 -0
  249. package/dist/terraform/graph.d.ts.map +1 -1
  250. package/dist/terraform/graph.js +10 -0
  251. package/dist/terraform/reach.d.ts +140 -0
  252. package/dist/terraform/reach.d.ts.map +1 -0
  253. package/dist/terraform/reach.js +170 -0
  254. package/dist/terraform/score.d.ts +14 -0
  255. package/dist/terraform/score.d.ts.map +1 -1
  256. package/dist/terraform/score.js +14 -31
  257. package/dist/terraform/state.d.ts.map +1 -1
  258. package/dist/terraform/state.js +4 -2
  259. package/dist/terraform/types.d.ts +8 -0
  260. package/dist/terraform/types.d.ts.map +1 -1
  261. package/dist/wave-blocked.d.ts +10 -1
  262. package/dist/wave-blocked.d.ts.map +1 -1
  263. package/dist/wave-blocked.js +31 -3
  264. package/dist/wave-runtime.mjs +264 -254
  265. package/dist/workspace/check.schema.json +13 -2
  266. package/dist/workspace/checks/ci-branch.d.ts +44 -0
  267. package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
  268. package/dist/workspace/checks/ci-branch.js +133 -0
  269. package/dist/workspace/checks/gate-approval.d.ts +58 -0
  270. package/dist/workspace/checks/gate-approval.d.ts.map +1 -0
  271. package/dist/workspace/checks/gate-approval.js +103 -0
  272. package/dist/workspace/checks/links.d.ts +3 -0
  273. package/dist/workspace/checks/links.d.ts.map +1 -1
  274. package/dist/workspace/checks/links.js +12 -1
  275. package/dist/workspace/checks/org.d.ts +48 -0
  276. package/dist/workspace/checks/org.d.ts.map +1 -0
  277. package/dist/workspace/checks/org.js +156 -0
  278. package/dist/workspace/checks.d.ts +12 -1
  279. package/dist/workspace/checks.d.ts.map +1 -1
  280. package/dist/workspace/checks.js +63 -7
  281. package/dist/workspace/ci-branches.d.ts +16 -0
  282. package/dist/workspace/ci-branches.d.ts.map +1 -1
  283. package/dist/workspace/ci-branches.js +27 -0
  284. package/dist/workspace/ci-green-cli.js +7 -7
  285. package/dist/workspace/ci-green-workflow.d.ts +1 -1
  286. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  287. package/dist/workspace/ci-green-workflow.js +13 -8
  288. package/dist/workspace/ci-green.d.ts +14 -0
  289. package/dist/workspace/ci-green.d.ts.map +1 -1
  290. package/dist/workspace/ci-green.js +44 -11
  291. package/dist/workspace/ci-run.d.ts +9 -1
  292. package/dist/workspace/ci-run.d.ts.map +1 -1
  293. package/dist/workspace/ci-run.js +67 -6
  294. package/dist/workspace/ci-serve.d.ts +9 -0
  295. package/dist/workspace/ci-serve.d.ts.map +1 -1
  296. package/dist/workspace/ci-serve.js +114 -76
  297. package/dist/workspace/compose-graph.d.ts +14 -0
  298. package/dist/workspace/compose-graph.d.ts.map +1 -1
  299. package/dist/workspace/composites.d.ts +4 -1
  300. package/dist/workspace/composites.d.ts.map +1 -1
  301. package/dist/workspace/composites.js +5 -2
  302. package/dist/workspace/composites.schema.json +22 -0
  303. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  304. package/dist/workspace/cross-root-lint.d.ts +56 -0
  305. package/dist/workspace/cross-root-lint.d.ts.map +1 -0
  306. package/dist/workspace/cross-root-lint.js +111 -0
  307. package/dist/workspace/dbt-models-reader.d.ts +44 -0
  308. package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
  309. package/dist/workspace/dbt-models-reader.js +356 -0
  310. package/dist/workspace/dbt-project-common.d.ts +40 -0
  311. package/dist/workspace/dbt-project-common.d.ts.map +1 -0
  312. package/dist/workspace/dbt-project-common.js +106 -0
  313. package/dist/workspace/dbt-project-reader.d.ts +4 -3
  314. package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
  315. package/dist/workspace/dbt-project-reader.js +14 -108
  316. package/dist/workspace/decision-rank.d.ts +26 -0
  317. package/dist/workspace/decision-rank.d.ts.map +1 -0
  318. package/dist/workspace/decision-rank.js +16 -0
  319. package/dist/workspace/declaration.d.ts +50 -2
  320. package/dist/workspace/declaration.d.ts.map +1 -1
  321. package/dist/workspace/declaration.js +57 -11
  322. package/dist/workspace/declaration.schema.json +166 -24
  323. package/dist/workspace/environments.d.ts +26 -2
  324. package/dist/workspace/environments.d.ts.map +1 -1
  325. package/dist/workspace/environments.js +26 -2
  326. package/dist/workspace/exit-cli.d.ts +44 -0
  327. package/dist/workspace/exit-cli.d.ts.map +1 -0
  328. package/dist/workspace/exit-cli.js +198 -0
  329. package/dist/workspace/exit.d.ts +159 -0
  330. package/dist/workspace/exit.d.ts.map +1 -0
  331. package/dist/workspace/exit.js +480 -0
  332. package/dist/workspace/exit.schema.json +257 -0
  333. package/dist/workspace/flyway-dir.d.ts +39 -0
  334. package/dist/workspace/flyway-dir.d.ts.map +1 -0
  335. package/dist/workspace/flyway-dir.js +104 -0
  336. package/dist/workspace/flyway-layout.d.ts +96 -0
  337. package/dist/workspace/flyway-layout.d.ts.map +1 -0
  338. package/dist/workspace/flyway-layout.js +308 -0
  339. package/dist/workspace/graph-cache.d.ts +3 -1
  340. package/dist/workspace/graph-cache.d.ts.map +1 -1
  341. package/dist/workspace/graph-cache.js +12 -1
  342. package/dist/workspace/graph-cli.d.ts.map +1 -1
  343. package/dist/workspace/graph-cli.js +26 -8
  344. package/dist/workspace/graph.schema.json +34 -0
  345. package/dist/workspace/import-fetch.d.ts +38 -0
  346. package/dist/workspace/import-fetch.d.ts.map +1 -0
  347. package/dist/workspace/import-fetch.js +99 -0
  348. package/dist/workspace/import-init.d.ts +26 -0
  349. package/dist/workspace/import-init.d.ts.map +1 -0
  350. package/dist/workspace/import-init.js +130 -0
  351. package/dist/workspace/import-source.d.ts +147 -0
  352. package/dist/workspace/import-source.d.ts.map +1 -0
  353. package/dist/workspace/import-source.js +370 -0
  354. package/dist/workspace/init.d.ts +4 -0
  355. package/dist/workspace/init.d.ts.map +1 -1
  356. package/dist/workspace/init.js +70 -3
  357. package/dist/workspace/intent.d.ts +2 -3
  358. package/dist/workspace/intent.d.ts.map +1 -1
  359. package/dist/workspace/intent.js +5 -9
  360. package/dist/workspace/kind-link-readers.d.ts +59 -2
  361. package/dist/workspace/kind-link-readers.d.ts.map +1 -1
  362. package/dist/workspace/kind-link-readers.js +17 -1
  363. package/dist/workspace/kind-readers.d.ts +6 -0
  364. package/dist/workspace/kind-readers.d.ts.map +1 -1
  365. package/dist/workspace/kind-readers.js +18 -0
  366. package/dist/workspace/kinds.d.ts +90 -3
  367. package/dist/workspace/kinds.d.ts.map +1 -1
  368. package/dist/workspace/kinds.js +161 -7
  369. package/dist/workspace/ledger.schema.json +158 -0
  370. package/dist/workspace/lexicon-outputs.d.ts +20 -1
  371. package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
  372. package/dist/workspace/lexicon-outputs.js +102 -10
  373. package/dist/workspace/lineage-check.d.ts +3 -0
  374. package/dist/workspace/lineage-check.d.ts.map +1 -1
  375. package/dist/workspace/lineage-check.js +23 -4
  376. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  377. package/dist/workspace/lineage-upgrade.js +13 -4
  378. package/dist/workspace/links.d.ts +8 -1
  379. package/dist/workspace/links.d.ts.map +1 -1
  380. package/dist/workspace/links.js +25 -1
  381. package/dist/workspace/ls.d.ts +8 -0
  382. package/dist/workspace/ls.d.ts.map +1 -1
  383. package/dist/workspace/ls.js +7 -1
  384. package/dist/workspace/ls.schema.json +7 -0
  385. package/dist/workspace/member-commands.d.ts.map +1 -1
  386. package/dist/workspace/member-commands.js +54 -5
  387. package/dist/workspace/migrate-links.d.ts +45 -0
  388. package/dist/workspace/migrate-links.d.ts.map +1 -0
  389. package/dist/workspace/migrate-links.js +100 -0
  390. package/dist/workspace/migration-dirs.d.ts +74 -0
  391. package/dist/workspace/migration-dirs.d.ts.map +1 -0
  392. package/dist/workspace/migration-dirs.js +254 -0
  393. package/dist/workspace/new-migrations.d.ts +47 -0
  394. package/dist/workspace/new-migrations.d.ts.map +1 -0
  395. package/dist/workspace/new-migrations.js +91 -0
  396. package/dist/workspace/pr-report.schema.json +46 -0
  397. package/dist/workspace/promote.d.ts +96 -0
  398. package/dist/workspace/promote.d.ts.map +1 -0
  399. package/dist/workspace/promote.js +263 -0
  400. package/dist/workspace/reason-codes.d.ts +21 -0
  401. package/dist/workspace/reason-codes.d.ts.map +1 -1
  402. package/dist/workspace/reason-codes.js +25 -0
  403. package/dist/workspace/records-cli.d.ts +11 -0
  404. package/dist/workspace/records-cli.d.ts.map +1 -1
  405. package/dist/workspace/records-cli.js +57 -5
  406. package/dist/workspace/records.schema.json +5 -0
  407. package/dist/workspace/release-candidates.d.ts +2 -0
  408. package/dist/workspace/release-candidates.d.ts.map +1 -1
  409. package/dist/workspace/release-candidates.js +5 -2
  410. package/dist/workspace/release-train.d.ts +53 -0
  411. package/dist/workspace/release-train.d.ts.map +1 -0
  412. package/dist/workspace/release-train.js +134 -0
  413. package/dist/workspace/release-workflow.d.ts.map +1 -1
  414. package/dist/workspace/release-workflow.js +4 -3
  415. package/dist/workspace/repo-ref.d.ts +45 -0
  416. package/dist/workspace/repo-ref.d.ts.map +1 -0
  417. package/dist/workspace/repo-ref.js +114 -0
  418. package/dist/workspace/run-event.schema.json +2 -2
  419. package/dist/workspace/runs-cli.d.ts +9 -0
  420. package/dist/workspace/runs-cli.d.ts.map +1 -1
  421. package/dist/workspace/runs-cli.js +48 -3
  422. package/dist/workspace/runs.schema.json +7 -0
  423. package/dist/workspace/search-contract.d.ts +1 -1
  424. package/dist/workspace/search-contract.d.ts.map +1 -1
  425. package/dist/workspace/status-decisions.d.ts +37 -0
  426. package/dist/workspace/status-decisions.d.ts.map +1 -0
  427. package/dist/workspace/status-decisions.js +147 -0
  428. package/dist/workspace/status-gates.d.ts +9 -0
  429. package/dist/workspace/status-gates.d.ts.map +1 -1
  430. package/dist/workspace/status.d.ts +4 -0
  431. package/dist/workspace/status.d.ts.map +1 -1
  432. package/dist/workspace/status.js +14 -2
  433. package/dist/workspace/status.schema.json +6 -0
  434. package/dist/workspace/work-cli.d.ts.map +1 -1
  435. package/dist/workspace/work-cli.js +5 -3
  436. package/dist/workspace/work-evidence-ops.d.ts +83 -0
  437. package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
  438. package/dist/workspace/work-evidence-ops.js +169 -0
  439. package/dist/workspace/work-evidence.d.ts +12 -2
  440. package/dist/workspace/work-evidence.d.ts.map +1 -1
  441. package/dist/workspace/work-evidence.js +21 -3
  442. package/dist/workspace/work-evidence.schema.json +49 -2
  443. package/dist/workspace/workspace-gates.d.ts +110 -0
  444. package/dist/workspace/workspace-gates.d.ts.map +1 -0
  445. package/dist/workspace/workspace-gates.js +140 -0
  446. package/dist/workspace/workspace-kinds.schema.json +108 -10
  447. package/dist/workspace/workspace-ledger.d.ts +76 -0
  448. package/dist/workspace/workspace-ledger.d.ts.map +1 -0
  449. package/dist/workspace/workspace-ledger.js +91 -0
  450. package/dist/workspace/workspace-run.d.ts +112 -0
  451. package/dist/workspace/workspace-run.d.ts.map +1 -0
  452. package/dist/workspace/workspace-run.js +432 -0
  453. package/package.json +1 -1
  454. package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  455. package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  456. package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  457. package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  458. package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  459. package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  460. package/src/__golden__/wave-loop/op-cost-member.json +13 -0
  461. package/src/__golden__/wave-loop/op-denied-source.json +16 -0
  462. package/src/__golden__/wave-loop/op-denied.json +16 -0
  463. package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  464. package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  465. package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
  466. package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
  467. package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
  468. package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  469. package/src/__golden__/wave-loop/op-held-on.json +16 -0
  470. package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  471. package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  472. package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  473. package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  474. package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
  475. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  476. package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
  477. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  478. package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
  479. package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
  480. package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  481. package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  482. package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
  483. package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  484. package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  485. package/src/__golden__/wave-loop/op-overridden.json +16 -0
  486. package/src/__golden__/wave-loop/op-per-run.json +20 -0
  487. package/src/__golden__/wave-loop/op-point.json +13 -0
  488. package/src/__golden__/wave-loop/op-pr-review.json +10 -0
  489. package/src/__golden__/wave-loop/op-sealed.json +10 -0
  490. package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
  491. package/src/__golden__/wave-loop/op-spend.json +10 -0
  492. package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
  493. package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
  494. package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
  495. package/src/__golden__/wave-loop/op-wide.json +39 -0
  496. package/src/action-pins.test.ts +78 -0
  497. package/src/action-pins.ts +92 -0
  498. package/src/cli/commands/carve-score-once.test.ts +99 -0
  499. package/src/cli/commands/carve.ts +37 -6
  500. package/src/cli/commands/check-lexicon.test.ts +2 -2
  501. package/src/cli/commands/import-report.ts +71 -0
  502. package/src/cli/commands/import.ts +33 -3
  503. package/src/cli/commands/migrate-dialect.test.ts +145 -0
  504. package/src/cli/commands/migrate.ts +132 -11
  505. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
  506. package/src/cli/handlers/approve.ts +33 -4
  507. package/src/cli/handlers/build.ts +7 -1
  508. package/src/cli/handlers/graph-reader.test.ts +31 -0
  509. package/src/cli/handlers/graph-reader.ts +51 -0
  510. package/src/cli/handlers/graph.ts +14 -0
  511. package/src/cli/handlers/ledger.ts +54 -0
  512. package/src/cli/handlers/migrate.ts +21 -12
  513. package/src/cli/handlers/misc.ts +10 -1
  514. package/src/cli/handlers/pr-env.ts +2 -2
  515. package/src/cli/handlers/pr.ts +176 -37
  516. package/src/cli/handlers/query.ts +16 -4
  517. package/src/cli/handlers/release.ts +6 -3
  518. package/src/cli/handlers/search.ts +13 -34
  519. package/src/cli/main.test.ts +17 -0
  520. package/src/cli/main.ts +87 -14
  521. package/src/cli/parse-args.ts +35 -2
  522. package/src/cli/registry.ts +22 -4
  523. package/src/components/builders.ts +8 -0
  524. package/src/components/capability-plugin-loader.ts +13 -1
  525. package/src/components/capability-plugin.ts +16 -0
  526. package/src/components/pr-env-run-bundle.test.ts +1 -0
  527. package/src/components/pr-hooks.test.ts +73 -5
  528. package/src/components/pr-hooks.ts +95 -5
  529. package/src/components/pr-pipeline.ts +64 -16
  530. package/src/components/pr-policy.test.ts +64 -1
  531. package/src/components/pr-policy.ts +53 -0
  532. package/src/components/pr-run.ts +4 -0
  533. package/src/components/registry.test.ts +1 -1
  534. package/src/components/starter-plugin.ts +6 -0
  535. package/src/components/verbs/index.ts +2 -0
  536. package/src/components/verbs/publish-artifact.test.ts +60 -0
  537. package/src/components/verbs/publish-artifact.ts +122 -0
  538. package/src/components/verbs/publish-image.test.ts +101 -0
  539. package/src/components/verbs/publish-image.ts +265 -0
  540. package/src/config-environments.ts +28 -1
  541. package/src/config.ts +24 -1
  542. package/src/gate-decision-bundle.test.ts +5 -2
  543. package/src/gate-decision-identity.test.ts +20 -0
  544. package/src/gate-decision.ts +46 -3
  545. package/src/graph-ir.ts +27 -0
  546. package/src/graph-walk.test.ts +44 -0
  547. package/src/graph-walk.ts +102 -0
  548. package/src/import/parser.ts +20 -0
  549. package/src/lexicon.ts +98 -6
  550. package/src/lifecycle/gate-ledger.ts +8 -0
  551. package/src/lifecycle/git.ts +2 -2
  552. package/src/lifecycle/ledger-read.test.ts +107 -0
  553. package/src/lifecycle/ledger-read.ts +195 -0
  554. package/src/lifecycle/release-workstation.ts +24 -0
  555. package/src/lifecycle/run-ledger.ts +5 -2
  556. package/src/lint/cross-root.test.ts +122 -0
  557. package/src/lint/discover.ts +3 -1
  558. package/src/lint/post-synth.ts +212 -2
  559. package/src/lint/rules/op/index.ts +2 -0
  560. package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
  561. package/src/op/activities/activity-contracts.ts +5 -2
  562. package/src/op/activities/index.ts +2 -2
  563. package/src/op/activities/review-agent.test.ts +221 -4
  564. package/src/op/activities/review-agent.ts +265 -22
  565. package/src/op/activities/work-evidence.ts +9 -1
  566. package/src/op/builders.ts +9 -5
  567. package/src/op/composites/apply-op.test.ts +108 -0
  568. package/src/op/composites/apply-op.ts +37 -2
  569. package/src/op/gate-approval-at-base.test.ts +97 -0
  570. package/src/op/gate-approval-base.ts +72 -0
  571. package/src/op/gate-approval.test.ts +6 -1
  572. package/src/op/gate-approval.ts +22 -27
  573. package/src/op/gate-name.ts +16 -1
  574. package/src/op/gate-quorum-at-base.test.ts +211 -0
  575. package/src/op/gate-quorum.ts +156 -8
  576. package/src/op/gate-review.ts +14 -0
  577. package/src/op/gate.ts +69 -9
  578. package/src/op/index.ts +3 -2
  579. package/src/op/local-executor.ts +91 -11
  580. package/src/op/op-waves-depends-on.test.ts +81 -0
  581. package/src/op/op-waves-hook-targets.test.ts +4 -2
  582. package/src/op/op-waves-quorum-product.test.ts +158 -0
  583. package/src/op/op-waves-run.ts +288 -14
  584. package/src/op/op-waves.ts +143 -14
  585. package/src/op/runtime.ts +8 -0
  586. package/src/op/schedule-overdue.ts +33 -0
  587. package/src/op/stage-hooks.ts +13 -5
  588. package/src/op/workspace-run-env.ts +29 -0
  589. package/src/pr-forge-bundle.test.ts +2 -2
  590. package/src/pr-forge.test.ts +47 -0
  591. package/src/pr-forge.ts +68 -0
  592. package/src/pr-loop-bundle.test.ts +3 -2
  593. package/src/pr-loop.test.ts +52 -0
  594. package/src/pr-loop.ts +12 -0
  595. package/src/pr-migrations.ts +97 -0
  596. package/src/pr-post-open.e2e.test.ts +36 -0
  597. package/src/pr-post.test.ts +102 -3
  598. package/src/pr-post.ts +90 -2
  599. package/src/read-filter.test.ts +54 -0
  600. package/src/read-filter.ts +193 -0
  601. package/src/run-event.ts +1 -1
  602. package/src/stage-hooks-bundle.test.ts +1 -0
  603. package/src/terraform/carve.ts +52 -15
  604. package/src/terraform/cuts.test.ts +173 -0
  605. package/src/terraform/cuts.ts +188 -0
  606. package/src/terraform/folds.ts +44 -0
  607. package/src/terraform/graph.ts +9 -0
  608. package/src/terraform/radius.test.ts +90 -0
  609. package/src/terraform/reach.test.ts +146 -0
  610. package/src/terraform/reach.ts +247 -0
  611. package/src/terraform/score.ts +27 -29
  612. package/src/terraform/state.ts +3 -1
  613. package/src/terraform/types.ts +8 -0
  614. package/src/wave-blocked.ts +28 -3
  615. package/src/wave-loop-golden.test.ts +4 -3
  616. package/src/workspace/__fixtures__/import/spacelift.json +9 -0
  617. package/src/workspace/__fixtures__/import/tfc.json +13 -0
  618. package/src/workspace/behold-kinds.test.ts +2 -1
  619. package/src/workspace/check-contract.test.ts +8 -6
  620. package/src/workspace/check.schema.json +13 -2
  621. package/src/workspace/checks/ci-branch.test.ts +128 -0
  622. package/src/workspace/checks/ci-branch.ts +141 -0
  623. package/src/workspace/checks/gate-approval.test.ts +77 -0
  624. package/src/workspace/checks/gate-approval.ts +121 -0
  625. package/src/workspace/checks/links.ts +12 -1
  626. package/src/workspace/checks/org.ts +169 -0
  627. package/src/workspace/checks.ts +68 -7
  628. package/src/workspace/ci-branches.ts +31 -0
  629. package/src/workspace/ci-green-branches.test.ts +153 -0
  630. package/src/workspace/ci-green-cli.ts +7 -7
  631. package/src/workspace/ci-green-workflow.test.ts +4 -3
  632. package/src/workspace/ci-green-workflow.ts +13 -9
  633. package/src/workspace/ci-green.ts +49 -12
  634. package/src/workspace/ci-run.test.ts +72 -1
  635. package/src/workspace/ci-run.ts +68 -8
  636. package/src/workspace/ci-serve.test.ts +95 -0
  637. package/src/workspace/ci-serve.ts +106 -65
  638. package/src/workspace/component-environments.test.ts +25 -2
  639. package/src/workspace/compose-graph.ts +9 -2
  640. package/src/workspace/composites.schema.json +22 -0
  641. package/src/workspace/composites.test.ts +39 -8
  642. package/src/workspace/composites.ts +6 -2
  643. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  644. package/src/workspace/cross-root-lint.test.ts +90 -0
  645. package/src/workspace/cross-root-lint.ts +126 -0
  646. package/src/workspace/dbt-kind.test.ts +3 -2
  647. package/src/workspace/dbt-models-reader.ts +362 -0
  648. package/src/workspace/dbt-models.test.ts +248 -0
  649. package/src/workspace/dbt-project-common.ts +113 -0
  650. package/src/workspace/dbt-project-reader.ts +29 -112
  651. package/src/workspace/decision-rank.ts +40 -0
  652. package/src/workspace/declaration.schema.json +166 -24
  653. package/src/workspace/declaration.ts +101 -15
  654. package/src/workspace/environments.ts +40 -4
  655. package/src/workspace/exit-cli.ts +219 -0
  656. package/src/workspace/exit.schema.json +257 -0
  657. package/src/workspace/exit.test.ts +185 -0
  658. package/src/workspace/exit.ts +560 -0
  659. package/src/workspace/flyway-dir.ts +113 -0
  660. package/src/workspace/flyway-layout.ts +337 -0
  661. package/src/workspace/graph-cache.ts +12 -2
  662. package/src/workspace/graph-cli.ts +25 -10
  663. package/src/workspace/graph-contract.test.ts +4 -2
  664. package/src/workspace/graph.schema.json +34 -0
  665. package/src/workspace/identity.test.ts +29 -2
  666. package/src/workspace/import-fetch.ts +151 -0
  667. package/src/workspace/import-init.ts +147 -0
  668. package/src/workspace/import-source.test.ts +183 -0
  669. package/src/workspace/import-source.ts +462 -0
  670. package/src/workspace/init.test.ts +55 -1
  671. package/src/workspace/init.ts +66 -4
  672. package/src/workspace/intent.ts +6 -14
  673. package/src/workspace/kind-link-readers.ts +68 -3
  674. package/src/workspace/kind-readers.test.ts +36 -0
  675. package/src/workspace/kind-readers.ts +18 -1
  676. package/src/workspace/kinds.test.ts +35 -7
  677. package/src/workspace/kinds.ts +212 -11
  678. package/src/workspace/ledger.schema.json +158 -0
  679. package/src/workspace/lexicon-outputs.ts +132 -13
  680. package/src/workspace/lineage-check.ts +30 -7
  681. package/src/workspace/lineage-upgrade.test.ts +20 -0
  682. package/src/workspace/lineage-upgrade.ts +16 -7
  683. package/src/workspace/links.ts +24 -1
  684. package/src/workspace/ls-contract.test.ts +1 -1
  685. package/src/workspace/ls.schema.json +7 -0
  686. package/src/workspace/ls.ts +10 -1
  687. package/src/workspace/member-commands.ts +60 -2
  688. package/src/workspace/member-file-schema.test.ts +124 -0
  689. package/src/workspace/migrate-links.test.ts +99 -0
  690. package/src/workspace/migrate-links.ts +108 -0
  691. package/src/workspace/migration-dirs.test.ts +272 -0
  692. package/src/workspace/migration-dirs.ts +267 -0
  693. package/src/workspace/new-migrations.test.ts +98 -0
  694. package/src/workspace/new-migrations.ts +109 -0
  695. package/src/workspace/org-scope.test.ts +238 -0
  696. package/src/workspace/pr-report.schema.json +46 -0
  697. package/src/workspace/promote.test.ts +114 -0
  698. package/src/workspace/promote.ts +302 -0
  699. package/src/workspace/reason-codes.test.ts +10 -1
  700. package/src/workspace/reason-codes.ts +25 -0
  701. package/src/workspace/records-cli.ts +58 -5
  702. package/src/workspace/records.schema.json +5 -0
  703. package/src/workspace/release-candidates.ts +7 -2
  704. package/src/workspace/release-train.ts +157 -0
  705. package/src/workspace/release-workflow.ts +4 -3
  706. package/src/workspace/repo-ref.ts +113 -0
  707. package/src/workspace/run-event.schema.json +2 -2
  708. package/src/workspace/runs-cli.ts +48 -4
  709. package/src/workspace/runs.schema.json +7 -0
  710. package/src/workspace/status-contract.test.ts +46 -1
  711. package/src/workspace/status-decisions.test.ts +100 -0
  712. package/src/workspace/status-decisions.ts +151 -0
  713. package/src/workspace/status-gates.ts +9 -0
  714. package/src/workspace/status.schema.json +6 -0
  715. package/src/workspace/status.ts +17 -3
  716. package/src/workspace/where-filters.test.ts +39 -0
  717. package/src/workspace/work-cli.ts +5 -3
  718. package/src/workspace/work-evidence-ops.test.ts +176 -0
  719. package/src/workspace/work-evidence-ops.ts +189 -0
  720. package/src/workspace/work-evidence.schema.json +49 -2
  721. package/src/workspace/work-evidence.ts +22 -3
  722. package/src/workspace/workspace-gates.ts +200 -0
  723. package/src/workspace/workspace-kinds.schema.json +108 -10
  724. package/src/workspace/workspace-ledger.ts +130 -0
  725. package/src/workspace/workspace-run.test.ts +322 -0
  726. package/src/workspace/workspace-run.ts +461 -0
@@ -0,0 +1,146 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { join } from "path";
3
+ import { loadHcl2json, parseTerraformDir } from "./parse";
4
+ import { carveIndex, reach } from "./reach";
5
+ import type { TfEdge, TfGraph, TfNode } from "./types";
6
+
7
+ /**
8
+ * #2325: transitive reachability over the carve graph. The fixture assertions
9
+ * run against depth-estate (#2323) as the production parse builds it; the
10
+ * cycle and cross-stack cases need graphs Terraform would not produce, so they
11
+ * are hand-built.
12
+ */
13
+ let parserAvailable = false;
14
+ try {
15
+ await loadHcl2json();
16
+ parserAvailable = true;
17
+ } catch {
18
+ parserAvailable = false;
19
+ }
20
+
21
+ const ESTATE = join(__dirname, "__fixtures__", "depth-estate");
22
+ const STATE = join(ESTATE, "terraform.tfstate");
23
+
24
+ const node = (address: string, extra: Partial<TfNode> = {}): TfNode => {
25
+ const [type, name] = address.split(".");
26
+ return { address, kind: "resource", type, name, instances: 1, hasDynamic: false, ...extra };
27
+ };
28
+ const edge = (from: string, to: string, extra: Partial<TfEdge> = {}): TfEdge => ({ from, to, attrs: [], via: [], ...extra });
29
+
30
+ describe("reach against depth-estate", () => {
31
+ test("the subnet group's radius is the cluster, its instance and the Lambda, plus one output, two deep", async () => {
32
+ if (!parserAvailable) return;
33
+ const graph = await parseTerraformDir(ESTATE);
34
+ expect(reach(graph, "aws_db_subnet_group.main")).toEqual({
35
+ address: "aws_db_subnet_group.main",
36
+ resources: ["aws_lambda_function.api", "aws_rds_cluster.main", "aws_rds_cluster_instance.one"],
37
+ outputs: ["output.db_endpoint"],
38
+ depth: 2,
39
+ crossStack: [],
40
+ hidden: [],
41
+ });
42
+ const vpc = reach(graph, "aws_vpc.main");
43
+ expect(vpc.resources).toHaveLength(13);
44
+ expect(vpc.outputs).toEqual(["output.db_endpoint", "output.lb_dns"]);
45
+ expect(vpc.depth).toBe(3);
46
+ });
47
+
48
+ test("folds contract first: the bucket's radius is the Lambda alone, not its own versioning block", async () => {
49
+ if (!parserAvailable) return;
50
+ const graph = await parseTerraformDir(ESTATE);
51
+ // The uncontracted graph has the versioning block reading the bucket.
52
+ expect(graph.edges.some((e) => e.from === "aws_s3_bucket_versioning.assets" && e.to === "aws_s3_bucket.assets")).toBe(true);
53
+ expect(reach(graph, "aws_s3_bucket.assets").resources).toEqual(["aws_lambda_function.api"]);
54
+ // Asking from the folded child walks from its parent.
55
+ expect(reach(graph, "aws_s3_bucket_versioning.assets").address).toBe("aws_s3_bucket.assets");
56
+ expect(carveIndex(graph).membersOf("aws_s3_bucket.assets")).toEqual(["aws_s3_bucket.assets", "aws_s3_bucket_versioning.assets"]);
57
+ });
58
+
59
+ test("outputs are counted apart and end the walk", async () => {
60
+ if (!parserAvailable) return;
61
+ const graph = await parseTerraformDir(ESTATE);
62
+ const lb = reach(graph, "aws_lb.public");
63
+ expect(lb.resources).toEqual(["aws_lb_listener.https"]);
64
+ expect(lb.outputs).toEqual(["output.lb_dns"]);
65
+ expect(reach(graph, "aws_lb_listener.https")).toMatchObject({ resources: [], outputs: [], depth: 0 });
66
+ });
67
+
68
+ test("a module and an unresolved count are named as hiding their contents; --state resolves the count", async () => {
69
+ if (!parserAvailable) return;
70
+ const bare = await parseTerraformDir(ESTATE);
71
+ expect(reach(bare, "aws_vpc.main").hidden).toEqual(["aws_subnet.private", "module.platform"]);
72
+ const withState = await parseTerraformDir(ESTATE, { statePath: STATE });
73
+ expect(reach(withState, "aws_vpc.main").hidden).toEqual(["module.platform"]);
74
+ });
75
+
76
+ test("direction out walks what a node needs", async () => {
77
+ if (!parserAvailable) return;
78
+ const graph = await parseTerraformDir(ESTATE);
79
+ expect(reach(graph, "aws_rds_cluster_instance.one", { direction: "out" }).resources).toEqual([
80
+ "aws_db_subnet_group.main",
81
+ "aws_kms_key.data",
82
+ "aws_rds_cluster.main",
83
+ "aws_security_group.db",
84
+ "aws_subnet.a",
85
+ "aws_subnet.b",
86
+ "aws_vpc.main",
87
+ ]);
88
+ });
89
+ });
90
+
91
+ describe("reach on hand-built graphs", () => {
92
+ test("a depends_on cycle terminates and does not count the start", () => {
93
+ const graph: TfGraph = {
94
+ nodes: [node("aws_sqs_queue.a"), node("aws_sqs_queue.b"), node("aws_sqs_queue.c")],
95
+ edges: [
96
+ edge("aws_sqs_queue.a", "aws_sqs_queue.b", { via: ["depends_on"] }),
97
+ edge("aws_sqs_queue.b", "aws_sqs_queue.c", { via: ["depends_on"] }),
98
+ edge("aws_sqs_queue.c", "aws_sqs_queue.a", { via: ["depends_on"] }),
99
+ ],
100
+ };
101
+ expect(reach(graph, "aws_sqs_queue.a")).toMatchObject({ resources: ["aws_sqs_queue.b", "aws_sqs_queue.c"], depth: 2 });
102
+ });
103
+
104
+ test("what is reachable only through a crossStack edge is listed apart", () => {
105
+ const graph: TfGraph = {
106
+ nodes: [node("A/Bucket"), node("A/Fn"), node("B/Fn"), node("B/Alarm")],
107
+ edges: [
108
+ edge("A/Fn", "A/Bucket"),
109
+ edge("B/Fn", "A/Bucket", { crossStack: true }),
110
+ edge("B/Alarm", "B/Fn"),
111
+ ],
112
+ };
113
+ expect(reach(graph, "A/Bucket")).toMatchObject({ resources: ["A/Fn", "B/Alarm", "B/Fn"], crossStack: ["B/Alarm", "B/Fn"] });
114
+ });
115
+
116
+ test("an address the graph does not hold is an error, not an empty radius", () => {
117
+ expect(() => reach({ nodes: [], edges: [] }, "aws_vpc.nope")).toThrow(/not a resource or module/);
118
+ });
119
+
120
+ test("under 100 ms at 6 000 nodes, index included", () => {
121
+ // Layered like the spike's synthetic estates: 60 layers of 100, each node
122
+ // reading two nodes in the layer below, about 11 800 edges. Node i reads
123
+ // 2i and 2i+1 below it, so the dependents of one bottom node double per
124
+ // layer and a walk from it covers almost everything.
125
+ const layers = 60;
126
+ const width = 100;
127
+ const nodes: TfNode[] = [];
128
+ const edges: TfEdge[] = [];
129
+ for (let l = 0; l < layers; l++) {
130
+ for (let i = 0; i < width; i++) {
131
+ nodes.push(node(`aws_sqs_queue.l${l}_${i}`));
132
+ if (l > 0) {
133
+ edges.push(edge(`aws_sqs_queue.l${l}_${i}`, `aws_sqs_queue.l${l - 1}_${(2 * i) % width}`));
134
+ edges.push(edge(`aws_sqs_queue.l${l}_${i}`, `aws_sqs_queue.l${l - 1}_${(2 * i + 1) % width}`));
135
+ }
136
+ }
137
+ }
138
+ reach({ nodes, edges }, "aws_sqs_queue.l0_0"); // warm the JIT on a different graph object
139
+ const graph: TfGraph = { nodes, edges };
140
+ const started = performance.now();
141
+ const r = reach(graph, "aws_sqs_queue.l0_0");
142
+ const took = performance.now() - started;
143
+ expect(r.resources.length).toBeGreaterThan(5_000);
144
+ expect(took).toBeLessThan(100);
145
+ });
146
+ });
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Transitive reachability over the carve graph (#2325).
3
+ *
4
+ * `inboundEdges`/`outboundEdges` in `graph.ts` answer one hop with a linear
5
+ * scan, which is right for the scorer's depth-1 counts and wrong as the step of
6
+ * a walk: calling it per node per step is O(V²E). This module builds one
7
+ * index per graph instead, and walks it with the same breadth-first walk
8
+ * `chant search` uses (`graph-walk.ts`).
9
+ *
10
+ * Four rules from the spike (docs/design/carve-dependency-blast-radius-spike.md)
11
+ * hold for every walk here and for the set functions in `cuts.ts` built on it:
12
+ *
13
+ * - Folds contract first. A folded sub-resource is its parent for the walk, so
14
+ * `aws_s3_bucket_versioning.assets` is never a dependent of
15
+ * `aws_s3_bucket.assets`, and a node reading the child depends on the
16
+ * parent.
17
+ * - Outputs are terminal and counted apart. An `output.<name>` pseudo-address
18
+ * has no inbound edges, so it never extends a path; it is reported in its
19
+ * own list.
20
+ * - Modules and unresolved `count`/`for_each` hide how much a node holds. A
21
+ * `module.x` node is one node whatever it contains, and `count = 20` is one
22
+ * node until `--state` resolves it. They are walked through and named, so a
23
+ * caller can say `unknown` instead of a number.
24
+ * - Cross-stack hops are told apart. On the CDK path an `Fn::ImportValue` edge
25
+ * carries `crossStack: true`; what is reachable only through one is listed
26
+ * separately.
27
+ *
28
+ * Pure, like the rest of the carve analysis. `src/cdk/graph.ts` emits the same
29
+ * `TfGraph`, so one implementation covers both sources.
30
+ */
31
+
32
+ import { adjacencyOf, walkBreadthFirst, type Adjacency } from "../graph-walk";
33
+ import { computeFolds } from "./folds";
34
+ import type { TfEdge, TfGraph, TfNode } from "./types";
35
+
36
+ /** An edge with both ends contracted to their fold parents. `edge` is the edge as the graph holds it. */
37
+ export interface ContractedEdge extends TfEdge {
38
+ edge: TfEdge;
39
+ }
40
+
41
+ /** The index every walk over one graph shares. Built once per graph; see {@link carveIndex}. */
42
+ export interface CarveIndex {
43
+ graph: TfGraph;
44
+ /** Every node by address, folded sub-resources included. */
45
+ nodes: Map<string, TfNode>;
46
+ /** The address a walk sees for `address`: its fold parent, or itself. */
47
+ rep(address: string): string;
48
+ /** A walked node's own blocks: itself first, then what folds into it, sorted. */
49
+ membersOf(address: string): string[];
50
+ /** Contracted edges by both ends. An edge internal to one fold is dropped. */
51
+ adj: Adjacency<ContractedEdge>;
52
+ /** The graph has at least one `crossStack` edge (a CDK assembly of several stacks). */
53
+ crossStack: boolean;
54
+ }
55
+
56
+ const indexes = new WeakMap<TfGraph, CarveIndex>();
57
+
58
+ /**
59
+ * The fold-contracted adjacency index for a graph, built on first use and
60
+ * cached against the graph object. O(V+E) to build; every walk after that is
61
+ * O(V+E). A caller that mutates `graph.edges` after the first walk must pass a
62
+ * new graph object (`applyStateCounts` only touches node instance counts, which
63
+ * the index reads live).
64
+ */
65
+ export function carveIndex(graph: TfGraph): CarveIndex {
66
+ const cached = indexes.get(graph);
67
+ if (cached) return cached;
68
+ const nodes = new Map(graph.nodes.map((n) => [n.address, n]));
69
+ const parentOf = new Map<string, string>();
70
+ const { childrenOf } = computeFolds(graph);
71
+ for (const [parent, children] of childrenOf) for (const child of children) parentOf.set(child, parent);
72
+ const rep = (address: string): string => parentOf.get(address) ?? address;
73
+ const contracted: ContractedEdge[] = [];
74
+ for (const e of graph.edges) {
75
+ const from = rep(e.from);
76
+ const to = rep(e.to);
77
+ if (from === to) continue; // a folded child reading its own parent is not an edge of the fold
78
+ contracted.push({ ...e, from, to, edge: e });
79
+ }
80
+ const index: CarveIndex = {
81
+ graph,
82
+ nodes,
83
+ rep,
84
+ membersOf: (address) => [address, ...[...(childrenOf.get(address) ?? [])].sort()],
85
+ adj: adjacencyOf(contracted),
86
+ crossStack: graph.edges.some((e) => e.crossStack),
87
+ };
88
+ indexes.set(graph, index);
89
+ return index;
90
+ }
91
+
92
+ /** `output.<name>`: a pseudo-address `buildGraph` gives an output block's edges (#1638). */
93
+ export function isOutputAddress(address: string): boolean {
94
+ return address.startsWith("output.");
95
+ }
96
+
97
+ /**
98
+ * Whether a node stands for an amount of infrastructure the graph cannot see:
99
+ * a Terraform `module` block (one node, whatever it holds; state is not read
100
+ * inside modules), or a `count`/`for_each` block whose instance count no
101
+ * `.tfstate` resolved. A CDK L3 construct is also `kind: "module"`, but its
102
+ * members are listed and its address is a construct path, so it hides nothing.
103
+ */
104
+ export function hidesContents(node: TfNode): boolean {
105
+ if (node.kind === "module" && node.address.startsWith("module.")) return true;
106
+ return node.expansion !== undefined && node.instancesFrom !== "state";
107
+ }
108
+
109
+ /**
110
+ * Which way a walk steps: `in` follows who reads a node (its dependents), `out`
111
+ * what it reads, `both` either (weak connectivity, #3909). An output is
112
+ * terminal every way: it is reached, never stepped from.
113
+ */
114
+ export type WalkDirection = "in" | "out" | "both";
115
+
116
+ /**
117
+ * Walk from `seeds` (already contracted) and return every node reached with its
118
+ * shortest depth, seeds excluded. `sameStack` refuses `crossStack` edges.
119
+ */
120
+ export function walkFrom(
121
+ index: CarveIndex,
122
+ seeds: Iterable<string>,
123
+ direction: WalkDirection,
124
+ opts: { sameStack?: boolean } = {},
125
+ ): Map<string, number> {
126
+ const start = new Set(seeds);
127
+ const reached = new Map<string, number>();
128
+ const stepsAlong = (edges: ContractedEdge[] | undefined, far: "from" | "to"): { far: string; hops: never[] }[] =>
129
+ (edges ?? []).filter((e) => !(opts.sameStack && e.crossStack)).map((e) => ({ far: e[far], hops: [] }));
130
+ walkBreadthFirst<never>(start, {
131
+ steps: (id) => {
132
+ if (isOutputAddress(id) && !start.has(id)) return [];
133
+ const steps: { far: string; hops: never[] }[] = [];
134
+ if (direction !== "out") steps.push(...stepsAlong(index.adj.in.get(id), "from"));
135
+ if (direction !== "in") steps.push(...stepsAlong(index.adj.out.get(id), "to"));
136
+ return steps;
137
+ },
138
+ arrive: ({ id, depth }) => {
139
+ if (!start.has(id) && !reached.has(id)) reached.set(id, depth);
140
+ return false;
141
+ },
142
+ });
143
+ return reached;
144
+ }
145
+
146
+ /** What a walk from one node reached. Every list is sorted and holds contracted addresses. */
147
+ export interface Reach {
148
+ /** The node the walk started from: the requested address, or its fold parent. */
149
+ address: string;
150
+ /** Resource and module nodes reached, the start excluded. */
151
+ resources: string[];
152
+ /** `output.<name>` pseudo-addresses reached; terminal, never in `resources`. */
153
+ outputs: string[];
154
+ /** The longest shortest path to anything reached, outputs included; 0 when nothing was. */
155
+ depth: number;
156
+ /** Of `resources`, those reached only through a `crossStack` edge. Empty off the CDK path. */
157
+ crossStack: string[];
158
+ /** Of `resources`, those that hide how much they hold (see {@link hidesContents}). */
159
+ hidden: string[];
160
+ }
161
+
162
+ /**
163
+ * Everything reachable from `address` (#2325). Direction `in` (the default)
164
+ * walks dependents: what a change to `address` reaches, its blast radius.
165
+ * Direction `out` walks dependencies: what `address` needs.
166
+ *
167
+ * Throws when `address` is not a node of the graph.
168
+ */
169
+ export function reach(graph: TfGraph, address: string, opts: { direction?: WalkDirection } = {}): Reach {
170
+ const index = carveIndex(graph);
171
+ if (!index.nodes.has(address)) throw new Error(`reach: ${address} is not a resource or module in this graph`);
172
+ const start = index.rep(address);
173
+ const direction = opts.direction ?? "in";
174
+ const reached = walkFrom(index, [start], direction);
175
+ const sorted = [...reached.keys()].sort(cmp);
176
+ const resources = sorted.filter((a) => !isOutputAddress(a));
177
+ let crossStack: string[] = [];
178
+ if (index.crossStack) {
179
+ const inStack = walkFrom(index, [start], direction, { sameStack: true });
180
+ crossStack = resources.filter((a) => !inStack.has(a));
181
+ }
182
+ return {
183
+ address: start,
184
+ resources,
185
+ outputs: sorted.filter(isOutputAddress),
186
+ depth: Math.max(0, ...reached.values()),
187
+ crossStack,
188
+ hidden: resources.filter((a) => hidesContents(index.nodes.get(a)!)),
189
+ };
190
+ }
191
+
192
+ /** Code-point order, as `buildGraph` sorts: locale-independent. */
193
+ export function cmp(a: string, b: string): number {
194
+ return a < b ? -1 : a > b ? 1 : 0;
195
+ }
196
+
197
+ /**
198
+ * How far a change to one node reaches, as `carve advise` reports it (#2326):
199
+ * its own axis beside the peelability score, never a penalty term. Every score
200
+ * term is a unit of work (an inbound edge is one data-source patch); a
201
+ * transitive dependent is not work, nobody patches it, and its count can run
202
+ * far past what a 0-100 score carries.
203
+ */
204
+ export interface BlastRadius {
205
+ /**
206
+ * Resources and modules that depend on the node, directly or not, weighted by
207
+ * instance count when `--state` resolved it. `"unknown"` when one of them is
208
+ * a module or a `count`/`for_each` no state resolved: see `hiddenBy`.
209
+ */
210
+ resources: number | "unknown";
211
+ /** `output` blocks that read the node or one of its dependents. */
212
+ outputs: number;
213
+ /** Hops to the farthest dependent, outputs included; 0 when nothing depends on it. */
214
+ depth: number;
215
+ /**
216
+ * Of `resources`, those reachable only through a `crossStack` edge (a CDK
217
+ * `Fn::ImportValue`), weighted the same way. Present only when the graph has
218
+ * such an edge.
219
+ */
220
+ crossStack?: number | "unknown";
221
+ /** The dependents that make a count `"unknown"`. Present only when one does. */
222
+ hiddenBy?: string[];
223
+ }
224
+
225
+ /** The instances `addresses` stand for, or `"unknown"` when one of them hides its contents. */
226
+ export function weigh(index: CarveIndex, addresses: readonly string[]): number | "unknown" {
227
+ let total = 0;
228
+ for (const address of addresses) {
229
+ const node = index.nodes.get(address)!;
230
+ if (hidesContents(node)) return "unknown";
231
+ total += node.instances;
232
+ }
233
+ return total;
234
+ }
235
+
236
+ /** The {@link BlastRadius} of `address`: its {@link reach} through dependents, counted. */
237
+ export function blastRadius(graph: TfGraph, address: string): BlastRadius {
238
+ const index = carveIndex(graph);
239
+ const r = reach(graph, address);
240
+ return {
241
+ resources: weigh(index, r.resources),
242
+ outputs: r.outputs.length,
243
+ depth: r.depth,
244
+ ...(index.crossStack ? { crossStack: weigh(index, r.crossStack) } : {}),
245
+ ...(r.hidden.length ? { hiddenBy: r.hidden } : {}),
246
+ };
247
+ }
@@ -30,9 +30,11 @@
30
30
  * shared verbatim.
31
31
  */
32
32
 
33
- import { inboundEdges, outboundEdges } from "./graph";
34
- import { foldParentOf, resolveTier, type TierInfo } from "./tier-map";
35
- import type { TfGraph, TfNode } from "./types";
33
+ import { adjacencyOf, type Adjacency } from "../graph-walk";
34
+ import { computeFolds } from "./folds";
35
+ import { blastRadius, type BlastRadius } from "./reach";
36
+ import { resolveTier, type TierInfo } from "./tier-map";
37
+ import type { TfEdge, TfGraph, TfNode } from "./types";
36
38
 
37
39
  export type PeelabilityBand = "clean leaf" | "carvable w/ edits" | "leave in Terraform";
38
40
 
@@ -96,6 +98,12 @@ export interface Peelability {
96
98
  notes?: string[];
97
99
  /** The source units folded into this node (#1056). */
98
100
  members?: CarveUnitMember[];
101
+ /**
102
+ * How far a change to this node reaches through its dependents (#2326). Its
103
+ * own axis: it never feeds `score`, so `100 + sum(breakdown.penalties)`
104
+ * still reproduces the score.
105
+ */
106
+ radius?: BlastRadius;
99
107
  }
100
108
 
101
109
  /**
@@ -125,6 +133,12 @@ export interface ScoreOptions {
125
133
  */
126
134
  tierOf?: (type: string) => TierInfo | null;
127
135
  signalsFor?: (node: TfNode) => ScoreSignals;
136
+ /**
137
+ * Compute each node's blast radius (#2326). Default true; a caller that only
138
+ * wants the score (`boundaryReport`) turns it off rather than walk the graph
139
+ * once per node for a field it drops.
140
+ */
141
+ radius?: boolean;
128
142
  }
129
143
 
130
144
  const clamp = (n: number): number => Math.max(0, Math.min(100, n));
@@ -144,40 +158,18 @@ export function bandFor(score: number): PeelabilityBand {
144
158
  /** Module tier: a module maps to a chant composite — reshaping, so tier 2 by default. */
145
159
  const MODULE_TIER = 2 as const;
146
160
 
147
- /**
148
- * Build the set of addresses that fold into a parent present in the estate, plus
149
- * the reverse lookup (parent → its folded children), so a parent can discount
150
- * inbound edges coming from its own folded sub-resources.
151
- */
152
- function computeFolds(graph: TfGraph): { folded: Set<string>; childrenOf: Map<string, Set<string>> } {
153
- const byAddress = new Map(graph.nodes.map((n) => [n.address, n]));
154
- const folded = new Set<string>();
155
- const childrenOf = new Map<string, Set<string>>();
156
- for (const node of graph.nodes) {
157
- if (node.kind !== "resource" || !node.type) continue;
158
- const parentType = foldParentOf(node.type);
159
- if (!parentType) continue;
160
- const parentAddress = `${parentType}.${node.name}`;
161
- if (!byAddress.has(parentAddress)) continue; // sub-resource without its parent → score it on its own
162
- folded.add(node.address);
163
- if (!childrenOf.has(parentAddress)) childrenOf.set(parentAddress, new Set());
164
- childrenOf.get(parentAddress)!.add(node.address);
165
- }
166
- return { folded, childrenOf };
167
- }
168
-
169
161
  function scoreNode(
170
162
  node: TfNode,
171
- graph: TfGraph,
163
+ adj: Adjacency<TfEdge>,
172
164
  foldedChildren: Set<string>,
173
165
  opts: ScoreOptions,
174
166
  ): Peelability {
175
167
  // Inbound excludes edges from this node's own folded sub-resources, and
176
168
  // counts output blocks separately — they cost less to bridge (#1638).
177
- const inboundAll = inboundEdges(graph, node.address).filter((e) => !foldedChildren.has(e.from));
169
+ const inboundAll = (adj.in.get(node.address) ?? []).filter((e) => !foldedChildren.has(e.from));
178
170
  const inbound = inboundAll.filter((e) => e.fromKind !== "output").length;
179
171
  const outputs = inboundAll.length - inbound;
180
- const outbound = outboundEdges(graph, node.address).length;
172
+ const outbound = (adj.out.get(node.address) ?? []).length;
181
173
 
182
174
  const signals = opts.signalsFor?.(node) ?? {};
183
175
  const extra = { ...(signals.penalties ?? {}) };
@@ -247,11 +239,17 @@ function scoreNode(
247
239
  /**
248
240
  * Score every carvable node in the estate, ranked most-peelable first. Folded
249
241
  * sub-resources are omitted (they carve with their parent, not on their own).
242
+ * Each carries its blast radius (#2326) beside the score, not in it.
250
243
  */
251
244
  export function scoreEstate(graph: TfGraph, opts: ScoreOptions = {}): Peelability[] {
252
245
  const { folded, childrenOf } = computeFolds(graph);
246
+ // One edge index for the whole pass: a per-node edge filter is O(E) each, O(V·E) in all (#4391).
247
+ const adj = adjacencyOf(graph.edges);
253
248
  return graph.nodes
254
249
  .filter((n) => !folded.has(n.address))
255
- .map((n) => scoreNode(n, graph, childrenOf.get(n.address) ?? new Set(), opts))
250
+ .map((n) => {
251
+ const scored = scoreNode(n, adj, childrenOf.get(n.address) ?? new Set(), opts);
252
+ return opts.radius === false ? scored : { ...scored, radius: blastRadius(graph, n.address) };
253
+ })
256
254
  .sort((a, b) => b.score - a.score || (a.address < b.address ? -1 : a.address > b.address ? 1 : 0));
257
255
  }
@@ -81,7 +81,9 @@ export function readStateInstanceCounts(statePath: string): Map<string, number>
81
81
  export function applyStateCounts(graph: TfGraph, counts: Map<string, number>): TfGraph {
82
82
  for (const node of graph.nodes) {
83
83
  const n = counts.get(node.address);
84
- if (n !== undefined) node.instances = Math.max(1, n);
84
+ if (n === undefined) continue;
85
+ node.instances = Math.max(1, n);
86
+ node.instancesFrom = "state";
85
87
  }
86
88
  return graph;
87
89
  }
@@ -28,6 +28,14 @@ export interface TfNode {
28
28
  instances: number;
29
29
  /** `count`, `for_each`, a `data` source, or an unresolved interpolation is present. */
30
30
  hasDynamic: boolean;
31
+ /**
32
+ * The block's own expansion meta-argument, when it has one (#2325). Unlike
33
+ * `hasDynamic`, which a `data` read also sets, this says the node stands for
34
+ * an unknown number of instances until state resolves it.
35
+ */
36
+ expansion?: "count" | "for_each";
37
+ /** `"state"` once `applyStateCounts` read this node's instance count from a `.tfstate` (#2325). */
38
+ instancesFrom?: "state";
31
39
  /**
32
40
  * The resource's physical name, when it is a plain literal in the HCL (e.g.
33
41
  * `bucket = "myapp-assets-prod"`). Used to build the live-import selector on
@@ -44,7 +44,7 @@
44
44
  * later member is blocked by `wave-<k>`.
45
45
  */
46
46
  import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
47
- import { join } from "node:path";
47
+ import { basename, join } from "node:path";
48
48
 
49
49
  export const WAVE_OUTCOME_VERSION = 1;
50
50
 
@@ -239,7 +239,8 @@ function outcomeFiles(dirs: readonly string[]): string[] {
239
239
  * The outcome of each member of the waves before `wave`, as their jobs wrote
240
240
  * them under `dirs`, and the earliest such wave that holds every later
241
241
  * member, if any. For a member more than one job wrote (a re-run, or a split
242
- * wave's deciding job and the share that applied it), the newest. A file
242
+ * wave's deciding job and the share that applied it), the newest, and on a
243
+ * tie the share's rather than the deciding job's `decided`. A file
243
244
  * that does not parse, or is of another version, is skipped.
244
245
  */
245
246
  export function earlierWaveOutcomes(
@@ -260,7 +261,8 @@ export function earlierWaveOutcomes(
260
261
  if (typeof members !== "object" || members === null) continue;
261
262
  for (const [member, o] of Object.entries(members)) {
262
263
  const had = out.get(member);
263
- if (had && had.at > f.at) continue;
264
+ // #4322: at the same time, what a share reported wins over its deciding job's `decided`.
265
+ if (had && (had.at > f.at || (had.at === f.at && o.status === "decided" && had.status !== "decided"))) continue;
264
266
  out.set(member, { ...o, wave: f.wave!, at: f.at });
265
267
  }
266
268
  const n = newest.get(f.wave!);
@@ -269,3 +271,26 @@ export function earlierWaveOutcomes(
269
271
  const holding = [...newest].filter(([, n]) => n.holdsAll).map(([k]) => k).sort((a, b) => a - b);
270
272
  return { members: out, ...(holding.length ? { holdsAll: holding[0] } : {}) };
271
273
  }
274
+
275
+ /**
276
+ * The newest outcome file named `name` under `dirs` (#4322), as
277
+ * {@link waveOutcomeFileName} names one job's: a split wave's share job
278
+ * reads its deciding job's, `outcome-wave-<k>.json`, to learn which runs that
279
+ * job decided and which it blocked. Undefined when there is none, or none
280
+ * that parses at this version.
281
+ */
282
+ export function readWaveOutcomeFile(dirs: readonly string[], name: string): WaveOutcomeFile | undefined {
283
+ let newest: WaveOutcomeFile | undefined;
284
+ for (const file of outcomeFiles(dirs)) {
285
+ if (basename(file) !== name) continue;
286
+ let f: Partial<WaveOutcomeFile>;
287
+ try {
288
+ f = JSON.parse(readFileSync(file, "utf-8"));
289
+ } catch {
290
+ continue;
291
+ }
292
+ if (f?.version !== WAVE_OUTCOME_VERSION || typeof f.at !== "string" || typeof f.members !== "object" || f.members === null) continue;
293
+ if (!newest || newest.at <= f.at) newest = f as WaveOutcomeFile;
294
+ }
295
+ return newest;
296
+ }
@@ -262,9 +262,10 @@ describe("Op waves golden transcripts (#3766)", () => {
262
262
  { wave: 1, label: "waits" },
263
263
  { wave: 1, label: "approved", gates: memoryGateLedgerPort({ resolutions: [resolution("tf-apply", "wave-1", set)] }) },
264
264
  { wave: 1, label: "another digest approved", gates: memoryGateLedgerPort({ resolutions: [resolution("tf-apply", "wave-1", digest("9"))] }) },
265
- { wave: 2, decide: true, label: "decide a wide never wave" },
266
- { wave: 2, share: 1, label: "share 1" },
267
- { wave: 2, share: 2, label: "share 2" },
265
+ // #4322: wave 1's last job here waits, which would stop wave 2; the wide wave keeps its outcomes apart to show its own jobs.
266
+ { wave: 2, decide: true, label: "decide a wide never wave", outcomeDir: "wide-outcomes" },
267
+ { wave: 2, share: 1, label: "share 1", outcomeDir: "wide-outcomes" },
268
+ { wave: 2, share: 2, label: "share 2", outcomeDir: "wide-outcomes" },
268
269
  ]);
269
270
  });
270
271
 
@@ -0,0 +1,9 @@
1
+ {
2
+ "account": "acme",
3
+ "stacks": [
4
+ { "id": "vpc", "name": "VPC", "space": "root", "projectRoot": "terraform/vpc", "vendor": "terraform", "labels": ["env:prod"] },
5
+ { "id": "db", "name": "Database", "projectRoot": "terraform/db", "labels": ["env:prod"], "policies": ["p1"], "dependsOn": [{ "stack": "vpc", "references": [{ "outputName": "subnet_ids", "inputName": "TF_VAR_subnets" }] }] },
6
+ { "id": "cfg", "name": "ansible-config", "vendor": "ansible", "projectRoot": "ansible", "dependsOn": [{ "stack": "db" }, { "stack": "nope" }], "contexts": ["c1"] }
7
+ ],
8
+ "policies": [{ "id": "p1", "name": "approval", "type": "APPROVAL" }]
9
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "organization": "acme",
3
+ "projects": [{ "id": "prj-1", "name": "platform" }],
4
+ "workspaces": [
5
+ { "id": "ws-net", "name": "network-prod", "project": "prj-1", "workingDirectory": "infra/network", "tags": ["env:prod"], "variables": [{ "key": "region" }, { "key": "token", "sensitive": true }], "vcsRepo": "acme/infra" },
6
+ { "id": "ws-app", "name": "app-prod", "project": "prj-1", "workingDirectory": "infra/app", "tags": ["env:prod"], "runTriggers": ["ws-net", "ws-gone"] },
7
+ { "id": "ws-app-dev", "name": "app-dev", "workingDirectory": "infra/app", "tags": ["environment:dev"], "runTriggers": ["network-prod"] }
8
+ ],
9
+ "policySets": [
10
+ { "name": "no-public-s3", "kind": "sentinel", "enforcement": "hard-mandatory", "workspaces": ["ws-app"] },
11
+ { "name": "tags-required", "global": true }
12
+ ]
13
+ }
@@ -46,6 +46,7 @@ function install(root: string, from: string): string {
46
46
  mkdirSync(dir, { recursive: true });
47
47
  cpSync(join(from, "package.json"), join(dir, "package.json"));
48
48
  cpSync(join(from, "workspace-kinds.json"), join(dir, "workspace-kinds.json"));
49
+ if (existsSync(join(from, "schemas"))) cpSync(join(from, "schemas"), join(dir, "schemas"), { recursive: true });
49
50
  const targets = new Set<string>(["./index.js"]);
50
51
  const collect = (entry: unknown): void => {
51
52
  if (typeof entry === "string") targets.add(entry.replace("*", "workspace-kinds"));
@@ -153,6 +154,6 @@ describe("behold's member kinds from their packages (#2545)", () => {
153
154
  await runWorkspaceLs({ args: parseArgs(["workspace", "ls", root, "--json"]), plugins: [] } as never);
154
155
  const doc = JSON.parse(out.join("\n")) as Extract<LsDocument, { members: unknown }>;
155
156
  expect(doc.members.map((m) => m.reason?.code ?? null)).toEqual(["unknown-kind", "unknown-kind", "unknown-kind", null]);
156
- expect(doc.members[0].reason?.message).toMatch(/known kinds: chant, design, other, workspace$/);
157
+ expect(doc.members[0].reason?.message).toMatch(/known kinds: chant, design, other, repo, workspace$/);
157
158
  });
158
159
  });