@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.
- package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
- package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
- package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
- package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
- package/dist/__golden__/wave-loop/op-denied.json +16 -0
- package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
- package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
- package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
- package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
- package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
- package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
- package/dist/__golden__/wave-loop/op-held-on.json +16 -0
- package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
- package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
- package/dist/__golden__/wave-loop/op-overridden.json +16 -0
- package/dist/__golden__/wave-loop/op-per-run.json +20 -0
- package/dist/__golden__/wave-loop/op-point.json +13 -0
- package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
- package/dist/__golden__/wave-loop/op-sealed.json +10 -0
- package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
- package/dist/__golden__/wave-loop/op-spend.json +10 -0
- package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
- package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
- package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
- package/dist/__golden__/wave-loop/op-wide.json +39 -0
- package/dist/action-pins.d.ts +61 -0
- package/dist/action-pins.d.ts.map +1 -0
- package/dist/action-pins.js +77 -0
- package/dist/cli/commands/carve.d.ts +3 -0
- package/dist/cli/commands/carve.d.ts.map +1 -1
- package/dist/cli/commands/carve.js +36 -7
- package/dist/cli/commands/import-report.d.ts +26 -0
- package/dist/cli/commands/import-report.d.ts.map +1 -0
- package/dist/cli/commands/import-report.js +65 -0
- package/dist/cli/commands/import.d.ts +11 -1
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +22 -3
- package/dist/cli/commands/migrate.d.ts +13 -1
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +134 -10
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +41 -14
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +6 -1
- package/dist/cli/handlers/graph-reader.d.ts +26 -0
- package/dist/cli/handlers/graph-reader.d.ts.map +1 -0
- package/dist/cli/handlers/graph-reader.js +41 -0
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +14 -0
- package/dist/cli/handlers/ledger.d.ts +3 -0
- package/dist/cli/handlers/ledger.d.ts.map +1 -0
- package/dist/cli/handlers/ledger.js +55 -0
- package/dist/cli/handlers/migrate.d.ts.map +1 -1
- package/dist/cli/handlers/migrate.js +18 -11
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +12 -1
- package/dist/cli/handlers/pr-env.js +2 -2
- package/dist/cli/handlers/pr.d.ts +15 -1
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +183 -40
- package/dist/cli/handlers/query.d.ts +7 -2
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +16 -4
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +6 -3
- package/dist/cli/handlers/search.d.ts +2 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +13 -39
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +87 -14
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +47 -2
- package/dist/cli/registry.d.ts +22 -4
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/builders.d.ts +5 -1
- package/dist/components/builders.d.ts.map +1 -1
- package/dist/components/builders.js +5 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +16 -1
- package/dist/components/capability-plugin.d.ts +14 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/pr-hooks.d.ts +21 -1
- package/dist/components/pr-hooks.d.ts.map +1 -1
- package/dist/components/pr-hooks.js +72 -4
- package/dist/components/pr-pipeline.d.ts +45 -10
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +47 -14
- package/dist/components/pr-policy.d.ts +36 -0
- package/dist/components/pr-policy.d.ts.map +1 -1
- package/dist/components/pr-policy.js +36 -0
- package/dist/components/pr-run.d.ts +3 -0
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +1 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +5 -1
- package/dist/components/verbs/index.d.ts +2 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +2 -0
- package/dist/components/verbs/publish-artifact.d.ts +65 -0
- package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
- package/dist/components/verbs/publish-artifact.js +74 -0
- package/dist/components/verbs/publish-image.d.ts +129 -0
- package/dist/components/verbs/publish-image.d.ts.map +1 -0
- package/dist/components/verbs/publish-image.js +145 -0
- package/dist/config-environments.d.ts +20 -0
- package/dist/config-environments.d.ts.map +1 -1
- package/dist/config-environments.js +12 -0
- package/dist/config.d.ts +25 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -2
- package/dist/gate-decision.d.ts +20 -2
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +30 -3
- package/dist/graph-ir.d.ts +25 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-walk.d.ts +66 -0
- package/dist/graph-walk.d.ts.map +1 -0
- package/dist/graph-walk.js +70 -0
- package/dist/import/parser.d.ts +19 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/lexicon.d.ts +94 -6
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +11 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +2 -5
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/ledger-read.d.ts +65 -0
- package/dist/lifecycle/ledger-read.d.ts.map +1 -0
- package/dist/lifecycle/ledger-read.js +160 -0
- package/dist/lifecycle/release-workstation.d.ts +7 -0
- package/dist/lifecycle/release-workstation.d.ts.map +1 -1
- package/dist/lifecycle/release-workstation.js +25 -0
- package/dist/lifecycle/run-ledger.d.ts +4 -3
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +1 -0
- package/dist/lint/discover.js +3 -1
- package/dist/lint/post-synth.d.ts +127 -1
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/post-synth.js +84 -2
- package/dist/lint/rules/op/index.d.ts +1 -0
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.js +2 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
- package/dist/op/activities/activity-contracts.d.ts +9 -6
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +5 -2
- package/dist/op/activities/index.d.ts +2 -2
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +1 -1
- package/dist/op/activities/review-agent.d.ts +90 -4
- package/dist/op/activities/review-agent.d.ts.map +1 -1
- package/dist/op/activities/review-agent.js +217 -19
- package/dist/op/activities/work-evidence.d.ts +8 -1
- package/dist/op/activities/work-evidence.d.ts.map +1 -1
- package/dist/op/activities/work-evidence.js +1 -0
- package/dist/op/builders.d.ts +9 -5
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +9 -5
- package/dist/op/composites/apply-op.d.ts +29 -2
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/apply-op.js +20 -1
- package/dist/op/gate-approval-base.d.ts +25 -0
- package/dist/op/gate-approval-base.d.ts.map +1 -0
- package/dist/op/gate-approval-base.js +71 -0
- package/dist/op/gate-approval.d.ts +18 -9
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-approval.js +13 -31
- package/dist/op/gate-name.d.ts +13 -0
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-name.js +14 -1
- package/dist/op/gate-quorum.d.ts +100 -3
- package/dist/op/gate-quorum.d.ts.map +1 -1
- package/dist/op/gate-quorum.js +108 -6
- package/dist/op/gate-review.d.ts +9 -0
- package/dist/op/gate-review.d.ts.map +1 -1
- package/dist/op/gate-review.js +13 -0
- package/dist/op/gate.d.ts +47 -5
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +25 -4
- package/dist/op/index.d.ts +3 -3
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +45 -3
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +68 -7
- package/dist/op/op-waves-run.d.ts +26 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +259 -13
- package/dist/op/op-waves.d.ts +72 -5
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +110 -15
- package/dist/op/runtime.d.ts +11 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/schedule-overdue.d.ts +16 -0
- package/dist/op/schedule-overdue.d.ts.map +1 -1
- package/dist/op/schedule-overdue.js +36 -0
- package/dist/op/stage-hooks.d.ts +9 -2
- package/dist/op/stage-hooks.d.ts.map +1 -1
- package/dist/op/stage-hooks.js +7 -5
- package/dist/op/workspace-run-env.d.ts +22 -0
- package/dist/op/workspace-run-env.d.ts.map +1 -0
- package/dist/op/workspace-run-env.js +21 -0
- package/dist/pr-forge.d.ts +24 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +47 -0
- package/dist/pr-loop.d.ts +10 -0
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +4 -0
- package/dist/pr-migrations.d.ts +62 -0
- package/dist/pr-migrations.d.ts.map +1 -0
- package/dist/pr-migrations.js +50 -0
- package/dist/pr-post.d.ts +42 -0
- package/dist/pr-post.d.ts.map +1 -1
- package/dist/pr-post.js +68 -2
- package/dist/read-filter.d.ts +65 -0
- package/dist/read-filter.d.ts.map +1 -0
- package/dist/read-filter.js +179 -0
- package/dist/run-event.d.ts +1 -1
- package/dist/run-event.d.ts.map +1 -1
- package/dist/terraform/carve.d.ts +24 -3
- package/dist/terraform/carve.d.ts.map +1 -1
- package/dist/terraform/carve.js +32 -14
- package/dist/terraform/cuts.d.ts +97 -0
- package/dist/terraform/cuts.d.ts.map +1 -0
- package/dist/terraform/cuts.js +138 -0
- package/dist/terraform/folds.d.ts +26 -0
- package/dist/terraform/folds.d.ts.map +1 -0
- package/dist/terraform/folds.js +38 -0
- package/dist/terraform/graph.d.ts.map +1 -1
- package/dist/terraform/graph.js +10 -0
- package/dist/terraform/reach.d.ts +140 -0
- package/dist/terraform/reach.d.ts.map +1 -0
- package/dist/terraform/reach.js +170 -0
- package/dist/terraform/score.d.ts +14 -0
- package/dist/terraform/score.d.ts.map +1 -1
- package/dist/terraform/score.js +14 -31
- package/dist/terraform/state.d.ts.map +1 -1
- package/dist/terraform/state.js +4 -2
- package/dist/terraform/types.d.ts +8 -0
- package/dist/terraform/types.d.ts.map +1 -1
- package/dist/wave-blocked.d.ts +10 -1
- package/dist/wave-blocked.d.ts.map +1 -1
- package/dist/wave-blocked.js +31 -3
- package/dist/wave-runtime.mjs +264 -254
- package/dist/workspace/check.schema.json +13 -2
- package/dist/workspace/checks/ci-branch.d.ts +44 -0
- package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
- package/dist/workspace/checks/ci-branch.js +133 -0
- package/dist/workspace/checks/gate-approval.d.ts +58 -0
- package/dist/workspace/checks/gate-approval.d.ts.map +1 -0
- package/dist/workspace/checks/gate-approval.js +103 -0
- package/dist/workspace/checks/links.d.ts +3 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/links.js +12 -1
- package/dist/workspace/checks/org.d.ts +48 -0
- package/dist/workspace/checks/org.d.ts.map +1 -0
- package/dist/workspace/checks/org.js +156 -0
- package/dist/workspace/checks.d.ts +12 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +63 -7
- package/dist/workspace/ci-branches.d.ts +16 -0
- package/dist/workspace/ci-branches.d.ts.map +1 -1
- package/dist/workspace/ci-branches.js +27 -0
- package/dist/workspace/ci-green-cli.js +7 -7
- package/dist/workspace/ci-green-workflow.d.ts +1 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +13 -8
- package/dist/workspace/ci-green.d.ts +14 -0
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +44 -11
- package/dist/workspace/ci-run.d.ts +9 -1
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +67 -6
- package/dist/workspace/ci-serve.d.ts +9 -0
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +114 -76
- package/dist/workspace/compose-graph.d.ts +14 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +4 -1
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/composites.js +5 -2
- package/dist/workspace/composites.schema.json +22 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
- package/dist/workspace/cross-root-lint.d.ts +56 -0
- package/dist/workspace/cross-root-lint.d.ts.map +1 -0
- package/dist/workspace/cross-root-lint.js +111 -0
- package/dist/workspace/dbt-models-reader.d.ts +44 -0
- package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
- package/dist/workspace/dbt-models-reader.js +356 -0
- package/dist/workspace/dbt-project-common.d.ts +40 -0
- package/dist/workspace/dbt-project-common.d.ts.map +1 -0
- package/dist/workspace/dbt-project-common.js +106 -0
- package/dist/workspace/dbt-project-reader.d.ts +4 -3
- package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
- package/dist/workspace/dbt-project-reader.js +14 -108
- package/dist/workspace/decision-rank.d.ts +26 -0
- package/dist/workspace/decision-rank.d.ts.map +1 -0
- package/dist/workspace/decision-rank.js +16 -0
- package/dist/workspace/declaration.d.ts +50 -2
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +57 -11
- package/dist/workspace/declaration.schema.json +166 -24
- package/dist/workspace/environments.d.ts +26 -2
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/environments.js +26 -2
- package/dist/workspace/exit-cli.d.ts +44 -0
- package/dist/workspace/exit-cli.d.ts.map +1 -0
- package/dist/workspace/exit-cli.js +198 -0
- package/dist/workspace/exit.d.ts +159 -0
- package/dist/workspace/exit.d.ts.map +1 -0
- package/dist/workspace/exit.js +480 -0
- package/dist/workspace/exit.schema.json +257 -0
- package/dist/workspace/flyway-dir.d.ts +39 -0
- package/dist/workspace/flyway-dir.d.ts.map +1 -0
- package/dist/workspace/flyway-dir.js +104 -0
- package/dist/workspace/flyway-layout.d.ts +96 -0
- package/dist/workspace/flyway-layout.d.ts.map +1 -0
- package/dist/workspace/flyway-layout.js +308 -0
- package/dist/workspace/graph-cache.d.ts +3 -1
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cache.js +12 -1
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +26 -8
- package/dist/workspace/graph.schema.json +34 -0
- package/dist/workspace/import-fetch.d.ts +38 -0
- package/dist/workspace/import-fetch.d.ts.map +1 -0
- package/dist/workspace/import-fetch.js +99 -0
- package/dist/workspace/import-init.d.ts +26 -0
- package/dist/workspace/import-init.d.ts.map +1 -0
- package/dist/workspace/import-init.js +130 -0
- package/dist/workspace/import-source.d.ts +147 -0
- package/dist/workspace/import-source.d.ts.map +1 -0
- package/dist/workspace/import-source.js +370 -0
- package/dist/workspace/init.d.ts +4 -0
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/init.js +70 -3
- package/dist/workspace/intent.d.ts +2 -3
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +5 -9
- package/dist/workspace/kind-link-readers.d.ts +59 -2
- package/dist/workspace/kind-link-readers.d.ts.map +1 -1
- package/dist/workspace/kind-link-readers.js +17 -1
- package/dist/workspace/kind-readers.d.ts +6 -0
- package/dist/workspace/kind-readers.d.ts.map +1 -1
- package/dist/workspace/kind-readers.js +18 -0
- package/dist/workspace/kinds.d.ts +90 -3
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/kinds.js +161 -7
- package/dist/workspace/ledger.schema.json +158 -0
- package/dist/workspace/lexicon-outputs.d.ts +20 -1
- package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
- package/dist/workspace/lexicon-outputs.js +102 -10
- package/dist/workspace/lineage-check.d.ts +3 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-check.js +23 -4
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.js +13 -4
- package/dist/workspace/links.d.ts +8 -1
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/links.js +25 -1
- package/dist/workspace/ls.d.ts +8 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +7 -1
- package/dist/workspace/ls.schema.json +7 -0
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +54 -5
- package/dist/workspace/migrate-links.d.ts +45 -0
- package/dist/workspace/migrate-links.d.ts.map +1 -0
- package/dist/workspace/migrate-links.js +100 -0
- package/dist/workspace/migration-dirs.d.ts +74 -0
- package/dist/workspace/migration-dirs.d.ts.map +1 -0
- package/dist/workspace/migration-dirs.js +254 -0
- package/dist/workspace/new-migrations.d.ts +47 -0
- package/dist/workspace/new-migrations.d.ts.map +1 -0
- package/dist/workspace/new-migrations.js +91 -0
- package/dist/workspace/pr-report.schema.json +46 -0
- package/dist/workspace/promote.d.ts +96 -0
- package/dist/workspace/promote.d.ts.map +1 -0
- package/dist/workspace/promote.js +263 -0
- package/dist/workspace/reason-codes.d.ts +21 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +25 -0
- package/dist/workspace/records-cli.d.ts +11 -0
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-cli.js +57 -5
- package/dist/workspace/records.schema.json +5 -0
- package/dist/workspace/release-candidates.d.ts +2 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -1
- package/dist/workspace/release-candidates.js +5 -2
- package/dist/workspace/release-train.d.ts +53 -0
- package/dist/workspace/release-train.d.ts.map +1 -0
- package/dist/workspace/release-train.js +134 -0
- package/dist/workspace/release-workflow.d.ts.map +1 -1
- package/dist/workspace/release-workflow.js +4 -3
- package/dist/workspace/repo-ref.d.ts +45 -0
- package/dist/workspace/repo-ref.d.ts.map +1 -0
- package/dist/workspace/repo-ref.js +114 -0
- package/dist/workspace/run-event.schema.json +2 -2
- package/dist/workspace/runs-cli.d.ts +9 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -1
- package/dist/workspace/runs-cli.js +48 -3
- package/dist/workspace/runs.schema.json +7 -0
- package/dist/workspace/search-contract.d.ts +1 -1
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/status-decisions.d.ts +37 -0
- package/dist/workspace/status-decisions.d.ts.map +1 -0
- package/dist/workspace/status-decisions.js +147 -0
- package/dist/workspace/status-gates.d.ts +9 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +4 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +14 -2
- package/dist/workspace/status.schema.json +6 -0
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-cli.js +5 -3
- package/dist/workspace/work-evidence-ops.d.ts +83 -0
- package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
- package/dist/workspace/work-evidence-ops.js +169 -0
- package/dist/workspace/work-evidence.d.ts +12 -2
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work-evidence.js +21 -3
- package/dist/workspace/work-evidence.schema.json +49 -2
- package/dist/workspace/workspace-gates.d.ts +110 -0
- package/dist/workspace/workspace-gates.d.ts.map +1 -0
- package/dist/workspace/workspace-gates.js +140 -0
- package/dist/workspace/workspace-kinds.schema.json +108 -10
- package/dist/workspace/workspace-ledger.d.ts +76 -0
- package/dist/workspace/workspace-ledger.d.ts.map +1 -0
- package/dist/workspace/workspace-ledger.js +91 -0
- package/dist/workspace/workspace-run.d.ts +112 -0
- package/dist/workspace/workspace-run.d.ts.map +1 -0
- package/dist/workspace/workspace-run.js +432 -0
- package/package.json +1 -1
- package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
- package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
- package/src/__golden__/wave-loop/op-cost-member.json +13 -0
- package/src/__golden__/wave-loop/op-denied-source.json +16 -0
- package/src/__golden__/wave-loop/op-denied.json +16 -0
- package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
- package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
- package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
- package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
- package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
- package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
- package/src/__golden__/wave-loop/op-held-on.json +16 -0
- package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
- package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
- package/src/__golden__/wave-loop/op-overridden.json +16 -0
- package/src/__golden__/wave-loop/op-per-run.json +20 -0
- package/src/__golden__/wave-loop/op-point.json +13 -0
- package/src/__golden__/wave-loop/op-pr-review.json +10 -0
- package/src/__golden__/wave-loop/op-sealed.json +10 -0
- package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
- package/src/__golden__/wave-loop/op-spend.json +10 -0
- package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
- package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
- package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
- package/src/__golden__/wave-loop/op-wide.json +39 -0
- package/src/action-pins.test.ts +78 -0
- package/src/action-pins.ts +92 -0
- package/src/cli/commands/carve-score-once.test.ts +99 -0
- package/src/cli/commands/carve.ts +37 -6
- package/src/cli/commands/check-lexicon.test.ts +2 -2
- package/src/cli/commands/import-report.ts +71 -0
- package/src/cli/commands/import.ts +33 -3
- package/src/cli/commands/migrate-dialect.test.ts +145 -0
- package/src/cli/commands/migrate.ts +132 -11
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
- package/src/cli/handlers/approve.ts +33 -4
- package/src/cli/handlers/build.ts +7 -1
- package/src/cli/handlers/graph-reader.test.ts +31 -0
- package/src/cli/handlers/graph-reader.ts +51 -0
- package/src/cli/handlers/graph.ts +14 -0
- package/src/cli/handlers/ledger.ts +54 -0
- package/src/cli/handlers/migrate.ts +21 -12
- package/src/cli/handlers/misc.ts +10 -1
- package/src/cli/handlers/pr-env.ts +2 -2
- package/src/cli/handlers/pr.ts +176 -37
- package/src/cli/handlers/query.ts +16 -4
- package/src/cli/handlers/release.ts +6 -3
- package/src/cli/handlers/search.ts +13 -34
- package/src/cli/main.test.ts +17 -0
- package/src/cli/main.ts +87 -14
- package/src/cli/parse-args.ts +35 -2
- package/src/cli/registry.ts +22 -4
- package/src/components/builders.ts +8 -0
- package/src/components/capability-plugin-loader.ts +13 -1
- package/src/components/capability-plugin.ts +16 -0
- package/src/components/pr-env-run-bundle.test.ts +1 -0
- package/src/components/pr-hooks.test.ts +73 -5
- package/src/components/pr-hooks.ts +95 -5
- package/src/components/pr-pipeline.ts +64 -16
- package/src/components/pr-policy.test.ts +64 -1
- package/src/components/pr-policy.ts +53 -0
- package/src/components/pr-run.ts +4 -0
- package/src/components/registry.test.ts +1 -1
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +2 -0
- package/src/components/verbs/publish-artifact.test.ts +60 -0
- package/src/components/verbs/publish-artifact.ts +122 -0
- package/src/components/verbs/publish-image.test.ts +101 -0
- package/src/components/verbs/publish-image.ts +265 -0
- package/src/config-environments.ts +28 -1
- package/src/config.ts +24 -1
- package/src/gate-decision-bundle.test.ts +5 -2
- package/src/gate-decision-identity.test.ts +20 -0
- package/src/gate-decision.ts +46 -3
- package/src/graph-ir.ts +27 -0
- package/src/graph-walk.test.ts +44 -0
- package/src/graph-walk.ts +102 -0
- package/src/import/parser.ts +20 -0
- package/src/lexicon.ts +98 -6
- package/src/lifecycle/gate-ledger.ts +8 -0
- package/src/lifecycle/git.ts +2 -2
- package/src/lifecycle/ledger-read.test.ts +107 -0
- package/src/lifecycle/ledger-read.ts +195 -0
- package/src/lifecycle/release-workstation.ts +24 -0
- package/src/lifecycle/run-ledger.ts +5 -2
- package/src/lint/cross-root.test.ts +122 -0
- package/src/lint/discover.ts +3 -1
- package/src/lint/post-synth.ts +212 -2
- package/src/lint/rules/op/index.ts +2 -0
- package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
- package/src/op/activities/activity-contracts.ts +5 -2
- package/src/op/activities/index.ts +2 -2
- package/src/op/activities/review-agent.test.ts +221 -4
- package/src/op/activities/review-agent.ts +265 -22
- package/src/op/activities/work-evidence.ts +9 -1
- package/src/op/builders.ts +9 -5
- package/src/op/composites/apply-op.test.ts +108 -0
- package/src/op/composites/apply-op.ts +37 -2
- package/src/op/gate-approval-at-base.test.ts +97 -0
- package/src/op/gate-approval-base.ts +72 -0
- package/src/op/gate-approval.test.ts +6 -1
- package/src/op/gate-approval.ts +22 -27
- package/src/op/gate-name.ts +16 -1
- package/src/op/gate-quorum-at-base.test.ts +211 -0
- package/src/op/gate-quorum.ts +156 -8
- package/src/op/gate-review.ts +14 -0
- package/src/op/gate.ts +69 -9
- package/src/op/index.ts +3 -2
- package/src/op/local-executor.ts +91 -11
- package/src/op/op-waves-depends-on.test.ts +81 -0
- package/src/op/op-waves-hook-targets.test.ts +4 -2
- package/src/op/op-waves-quorum-product.test.ts +158 -0
- package/src/op/op-waves-run.ts +288 -14
- package/src/op/op-waves.ts +143 -14
- package/src/op/runtime.ts +8 -0
- package/src/op/schedule-overdue.ts +33 -0
- package/src/op/stage-hooks.ts +13 -5
- package/src/op/workspace-run-env.ts +29 -0
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +47 -0
- package/src/pr-forge.ts +68 -0
- package/src/pr-loop-bundle.test.ts +3 -2
- package/src/pr-loop.test.ts +52 -0
- package/src/pr-loop.ts +12 -0
- package/src/pr-migrations.ts +97 -0
- package/src/pr-post-open.e2e.test.ts +36 -0
- package/src/pr-post.test.ts +102 -3
- package/src/pr-post.ts +90 -2
- package/src/read-filter.test.ts +54 -0
- package/src/read-filter.ts +193 -0
- package/src/run-event.ts +1 -1
- package/src/stage-hooks-bundle.test.ts +1 -0
- package/src/terraform/carve.ts +52 -15
- package/src/terraform/cuts.test.ts +173 -0
- package/src/terraform/cuts.ts +188 -0
- package/src/terraform/folds.ts +44 -0
- package/src/terraform/graph.ts +9 -0
- package/src/terraform/radius.test.ts +90 -0
- package/src/terraform/reach.test.ts +146 -0
- package/src/terraform/reach.ts +247 -0
- package/src/terraform/score.ts +27 -29
- package/src/terraform/state.ts +3 -1
- package/src/terraform/types.ts +8 -0
- package/src/wave-blocked.ts +28 -3
- package/src/wave-loop-golden.test.ts +4 -3
- package/src/workspace/__fixtures__/import/spacelift.json +9 -0
- package/src/workspace/__fixtures__/import/tfc.json +13 -0
- package/src/workspace/behold-kinds.test.ts +2 -1
- package/src/workspace/check-contract.test.ts +8 -6
- package/src/workspace/check.schema.json +13 -2
- package/src/workspace/checks/ci-branch.test.ts +128 -0
- package/src/workspace/checks/ci-branch.ts +141 -0
- package/src/workspace/checks/gate-approval.test.ts +77 -0
- package/src/workspace/checks/gate-approval.ts +121 -0
- package/src/workspace/checks/links.ts +12 -1
- package/src/workspace/checks/org.ts +169 -0
- package/src/workspace/checks.ts +68 -7
- package/src/workspace/ci-branches.ts +31 -0
- package/src/workspace/ci-green-branches.test.ts +153 -0
- package/src/workspace/ci-green-cli.ts +7 -7
- package/src/workspace/ci-green-workflow.test.ts +4 -3
- package/src/workspace/ci-green-workflow.ts +13 -9
- package/src/workspace/ci-green.ts +49 -12
- package/src/workspace/ci-run.test.ts +72 -1
- package/src/workspace/ci-run.ts +68 -8
- package/src/workspace/ci-serve.test.ts +95 -0
- package/src/workspace/ci-serve.ts +106 -65
- package/src/workspace/component-environments.test.ts +25 -2
- package/src/workspace/compose-graph.ts +9 -2
- package/src/workspace/composites.schema.json +22 -0
- package/src/workspace/composites.test.ts +39 -8
- package/src/workspace/composites.ts +6 -2
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
- package/src/workspace/cross-root-lint.test.ts +90 -0
- package/src/workspace/cross-root-lint.ts +126 -0
- package/src/workspace/dbt-kind.test.ts +3 -2
- package/src/workspace/dbt-models-reader.ts +362 -0
- package/src/workspace/dbt-models.test.ts +248 -0
- package/src/workspace/dbt-project-common.ts +113 -0
- package/src/workspace/dbt-project-reader.ts +29 -112
- package/src/workspace/decision-rank.ts +40 -0
- package/src/workspace/declaration.schema.json +166 -24
- package/src/workspace/declaration.ts +101 -15
- package/src/workspace/environments.ts +40 -4
- package/src/workspace/exit-cli.ts +219 -0
- package/src/workspace/exit.schema.json +257 -0
- package/src/workspace/exit.test.ts +185 -0
- package/src/workspace/exit.ts +560 -0
- package/src/workspace/flyway-dir.ts +113 -0
- package/src/workspace/flyway-layout.ts +337 -0
- package/src/workspace/graph-cache.ts +12 -2
- package/src/workspace/graph-cli.ts +25 -10
- package/src/workspace/graph-contract.test.ts +4 -2
- package/src/workspace/graph.schema.json +34 -0
- package/src/workspace/identity.test.ts +29 -2
- package/src/workspace/import-fetch.ts +151 -0
- package/src/workspace/import-init.ts +147 -0
- package/src/workspace/import-source.test.ts +183 -0
- package/src/workspace/import-source.ts +462 -0
- package/src/workspace/init.test.ts +55 -1
- package/src/workspace/init.ts +66 -4
- package/src/workspace/intent.ts +6 -14
- package/src/workspace/kind-link-readers.ts +68 -3
- package/src/workspace/kind-readers.test.ts +36 -0
- package/src/workspace/kind-readers.ts +18 -1
- package/src/workspace/kinds.test.ts +35 -7
- package/src/workspace/kinds.ts +212 -11
- package/src/workspace/ledger.schema.json +158 -0
- package/src/workspace/lexicon-outputs.ts +132 -13
- package/src/workspace/lineage-check.ts +30 -7
- package/src/workspace/lineage-upgrade.test.ts +20 -0
- package/src/workspace/lineage-upgrade.ts +16 -7
- package/src/workspace/links.ts +24 -1
- package/src/workspace/ls-contract.test.ts +1 -1
- package/src/workspace/ls.schema.json +7 -0
- package/src/workspace/ls.ts +10 -1
- package/src/workspace/member-commands.ts +60 -2
- package/src/workspace/member-file-schema.test.ts +124 -0
- package/src/workspace/migrate-links.test.ts +99 -0
- package/src/workspace/migrate-links.ts +108 -0
- package/src/workspace/migration-dirs.test.ts +272 -0
- package/src/workspace/migration-dirs.ts +267 -0
- package/src/workspace/new-migrations.test.ts +98 -0
- package/src/workspace/new-migrations.ts +109 -0
- package/src/workspace/org-scope.test.ts +238 -0
- package/src/workspace/pr-report.schema.json +46 -0
- package/src/workspace/promote.test.ts +114 -0
- package/src/workspace/promote.ts +302 -0
- package/src/workspace/reason-codes.test.ts +10 -1
- package/src/workspace/reason-codes.ts +25 -0
- package/src/workspace/records-cli.ts +58 -5
- package/src/workspace/records.schema.json +5 -0
- package/src/workspace/release-candidates.ts +7 -2
- package/src/workspace/release-train.ts +157 -0
- package/src/workspace/release-workflow.ts +4 -3
- package/src/workspace/repo-ref.ts +113 -0
- package/src/workspace/run-event.schema.json +2 -2
- package/src/workspace/runs-cli.ts +48 -4
- package/src/workspace/runs.schema.json +7 -0
- package/src/workspace/status-contract.test.ts +46 -1
- package/src/workspace/status-decisions.test.ts +100 -0
- package/src/workspace/status-decisions.ts +151 -0
- package/src/workspace/status-gates.ts +9 -0
- package/src/workspace/status.schema.json +6 -0
- package/src/workspace/status.ts +17 -3
- package/src/workspace/where-filters.test.ts +39 -0
- package/src/workspace/work-cli.ts +5 -3
- package/src/workspace/work-evidence-ops.test.ts +176 -0
- package/src/workspace/work-evidence-ops.ts +189 -0
- package/src/workspace/work-evidence.schema.json +49 -2
- package/src/workspace/work-evidence.ts +22 -3
- package/src/workspace/workspace-gates.ts +200 -0
- package/src/workspace/workspace-kinds.schema.json +108 -10
- package/src/workspace/workspace-ledger.ts +130 -0
- package/src/workspace/workspace-run.test.ts +322 -0
- 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
|
+
}
|
package/src/terraform/score.ts
CHANGED
|
@@ -30,9 +30,11 @@
|
|
|
30
30
|
* shared verbatim.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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) =>
|
|
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
|
}
|
package/src/terraform/state.ts
CHANGED
|
@@ -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
|
|
84
|
+
if (n === undefined) continue;
|
|
85
|
+
node.instances = Math.max(1, n);
|
|
86
|
+
node.instancesFrom = "state";
|
|
85
87
|
}
|
|
86
88
|
return graph;
|
|
87
89
|
}
|
package/src/terraform/types.ts
CHANGED
|
@@ -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
|
package/src/wave-blocked.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
266
|
-
{ wave: 2,
|
|
267
|
-
{ wave: 2, share:
|
|
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
|
});
|