@intentius/chant 0.124.52 → 0.124.54
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/audit/discover.d.ts.map +1 -1
- package/dist/audit/discover.js +3 -1
- package/dist/behaviour.d.ts +1 -1
- package/dist/behaviour.d.ts.map +1 -1
- package/dist/capabilities/templates.d.ts +65 -0
- package/dist/capabilities/templates.d.ts.map +1 -0
- package/dist/capabilities/templates.js +193 -0
- package/dist/capabilities/templates.json +145 -0
- package/dist/change-set-graph.d.ts +35 -3
- package/dist/change-set-graph.d.ts.map +1 -1
- package/dist/change-set-graph.js +60 -9
- package/dist/change-set.d.ts +65 -7
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +23 -12
- package/dist/ci-identity.d.ts +7 -2
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +4 -1
- package/dist/cli/commands/import-index.d.ts +52 -0
- package/dist/cli/commands/import-index.d.ts.map +1 -0
- package/dist/cli/commands/import-index.js +226 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +4 -2
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/components.js +11 -2
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +16 -3
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +22 -4
- package/dist/cli/handlers/index-cloud.d.ts +14 -0
- package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
- package/dist/cli/handlers/index-cloud.js +22 -0
- package/dist/cli/handlers/index-diff.d.ts +18 -0
- package/dist/cli/handlers/index-diff.d.ts.map +1 -0
- package/dist/cli/handlers/index-diff.js +142 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-drift.js +64 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +188 -28
- package/dist/cli/handlers/misc.d.ts +9 -0
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +77 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +15 -2
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +89 -15
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +28 -4
- package/dist/cli/handlers/scenario.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.js +8 -7
- package/dist/cli/handlers/search.d.ts +34 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +216 -36
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +55 -1
- package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
- package/dist/cli/mcp/lifecycle-tools.js +3 -2
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.js +4 -3
- package/dist/cli/mcp/workspace-tools.js +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +55 -2
- package/dist/cli/registry.d.ts +19 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cloud-lister/aws.d.ts +35 -0
- package/dist/cloud-lister/aws.d.ts.map +1 -0
- package/dist/cloud-lister/aws.js +203 -0
- package/dist/cloud-lister/azure.d.ts +34 -0
- package/dist/cloud-lister/azure.d.ts.map +1 -0
- package/dist/cloud-lister/azure.js +129 -0
- package/dist/cloud-lister/common.d.ts +40 -0
- package/dist/cloud-lister/common.d.ts.map +1 -0
- package/dist/cloud-lister/common.js +83 -0
- package/dist/cloud-lister/credentials.d.ts +50 -0
- package/dist/cloud-lister/credentials.d.ts.map +1 -0
- package/dist/cloud-lister/credentials.js +86 -0
- package/dist/cloud-lister/fakes.d.ts +27 -0
- package/dist/cloud-lister/fakes.d.ts.map +1 -0
- package/dist/cloud-lister/fakes.js +50 -0
- package/dist/cloud-lister/gcp.d.ts +42 -0
- package/dist/cloud-lister/gcp.d.ts.map +1 -0
- package/dist/cloud-lister/gcp.js +143 -0
- package/dist/cloud-lister/guard.d.ts +49 -0
- package/dist/cloud-lister/guard.d.ts.map +1 -0
- package/dist/cloud-lister/guard.js +103 -0
- package/dist/cloud-lister/ids.d.ts +23 -0
- package/dist/cloud-lister/ids.d.ts.map +1 -0
- package/dist/cloud-lister/ids.js +42 -0
- package/dist/cloud-lister/index.d.ts +67 -0
- package/dist/cloud-lister/index.d.ts.map +1 -0
- package/dist/cloud-lister/index.js +102 -0
- package/dist/cloud-lister/kubernetes.d.ts +64 -0
- package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
- package/dist/cloud-lister/kubernetes.js +276 -0
- package/dist/cloud-lister/markers.d.ts +31 -0
- package/dist/cloud-lister/markers.d.ts.map +1 -0
- package/dist/cloud-lister/markers.js +73 -0
- package/dist/cloud-lister/mask.d.ts +31 -0
- package/dist/cloud-lister/mask.d.ts.map +1 -0
- package/dist/cloud-lister/mask.js +74 -0
- package/dist/cloud-lister/sigv4.d.ts +29 -0
- package/dist/cloud-lister/sigv4.d.ts.map +1 -0
- package/dist/cloud-lister/sigv4.js +54 -0
- package/dist/cloud-lister/transport.d.ts +55 -0
- package/dist/cloud-lister/transport.d.ts.map +1 -0
- package/dist/cloud-lister/transport.js +86 -0
- package/dist/cloud-lister/types.d.ts +121 -0
- package/dist/cloud-lister/types.d.ts.map +1 -0
- package/dist/cloud-lister/types.js +24 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +6 -0
- package/dist/components/capability-plugin.d.ts +9 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability.d.ts +215 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +21 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +58 -14
- package/dist/components/declared-reads.d.ts +38 -0
- package/dist/components/declared-reads.d.ts.map +1 -0
- package/dist/components/declared-reads.js +131 -0
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/deploy-units.js +4 -0
- package/dist/components/driver.d.ts +34 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +66 -3
- package/dist/components/fan-out-run.d.ts +23 -1
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +82 -14
- package/dist/components/fan-out-support.d.ts +6 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +4 -1
- package/dist/components/fan-out.d.ts +28 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/groups.d.ts +229 -0
- package/dist/components/groups.d.ts.map +1 -0
- package/dist/components/groups.js +760 -0
- package/dist/components/index.d.ts +6 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -0
- package/dist/components/plan-shape-conformance.d.ts +48 -0
- package/dist/components/plan-shape-conformance.d.ts.map +1 -0
- package/dist/components/plan-shape-conformance.js +122 -0
- package/dist/components/pr-run.d.ts +41 -4
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +209 -39
- package/dist/components/promote-rounds.d.ts +9 -0
- package/dist/components/promote-rounds.d.ts.map +1 -1
- package/dist/components/promote-rounds.js +23 -2
- package/dist/components/promote.d.ts +17 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +36 -6
- package/dist/components/release-gate.d.ts +23 -1
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +97 -33
- package/dist/components/run-groups.d.ts +58 -0
- package/dist/components/run-groups.d.ts.map +1 -0
- package/dist/components/run-groups.js +78 -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 +6 -1
- package/dist/components/verbs/index.d.ts +1 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +1 -0
- package/dist/components/verbs/member-outputs.d.ts +187 -0
- package/dist/components/verbs/member-outputs.d.ts.map +1 -0
- package/dist/components/verbs/member-outputs.js +218 -0
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -0
- package/dist/deep-observation.d.ts +1 -13
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/deep-observation.js +5 -22
- package/dist/drift-record.d.ts +542 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +886 -0
- package/dist/engine-facts.d.ts +144 -0
- package/dist/engine-facts.d.ts.map +1 -0
- package/dist/engine-facts.js +14 -0
- package/dist/graph-ir.d.ts +153 -2
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +224 -6
- package/dist/graph-refs.d.ts +145 -0
- package/dist/graph-refs.d.ts.map +1 -1
- package/dist/graph-refs.js +305 -0
- package/dist/graph-schema/fixtures/adopt.json +15 -0
- package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/dist/graph-schema/fixtures/index.json +68 -0
- package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/dist/graph-schema/fixtures.d.ts +23 -0
- package/dist/graph-schema/fixtures.d.ts.map +1 -0
- package/dist/graph-schema/fixtures.js +246 -0
- package/dist/graph-schema/generate.d.ts +15 -0
- package/dist/graph-schema/generate.d.ts.map +1 -0
- package/dist/graph-schema/generate.js +267 -0
- package/dist/graph-schema/graph.schema.json +1387 -0
- package/dist/health.schema.json +2 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/instance-nodes.d.ts +159 -0
- package/dist/instance-nodes.d.ts.map +1 -0
- package/dist/instance-nodes.js +231 -0
- package/dist/lexicon.d.ts +72 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/adoption-file.d.ts +56 -0
- package/dist/lifecycle/adoption-file.d.ts.map +1 -0
- package/dist/lifecycle/adoption-file.js +123 -0
- package/dist/lifecycle/cloud-index.d.ts +186 -0
- package/dist/lifecycle/cloud-index.d.ts.map +1 -0
- package/dist/lifecycle/cloud-index.js +618 -0
- package/dist/lifecycle/derive-edges.d.ts +59 -0
- package/dist/lifecycle/derive-edges.d.ts.map +1 -0
- package/dist/lifecycle/derive-edges.js +126 -0
- package/dist/lifecycle/git.d.ts +10 -9
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +7 -1
- package/dist/lifecycle/identity-match.d.ts +133 -0
- package/dist/lifecycle/identity-match.d.ts.map +1 -0
- package/dist/lifecycle/identity-match.js +147 -0
- package/dist/lifecycle/index-diff.d.ts +224 -0
- package/dist/lifecycle/index-diff.d.ts.map +1 -0
- package/dist/lifecycle/index-diff.js +327 -0
- package/dist/lifecycle/index-document.d.ts +130 -0
- package/dist/lifecycle/index-document.d.ts.map +1 -0
- package/dist/lifecycle/index-document.js +189 -0
- package/dist/lifecycle/index-drift.d.ts +121 -0
- package/dist/lifecycle/index-drift.d.ts.map +1 -0
- package/dist/lifecycle/index-drift.js +178 -0
- package/dist/lifecycle/index-import.d.ts +168 -0
- package/dist/lifecycle/index-import.d.ts.map +1 -0
- package/dist/lifecycle/index-import.js +515 -0
- package/dist/lifecycle/index-join.d.ts +129 -0
- package/dist/lifecycle/index-join.d.ts.map +1 -0
- package/dist/lifecycle/index-join.js +248 -0
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/observation-baseline.d.ts +61 -21
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/observation-baseline.js +144 -19
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +6 -1
- package/dist/lifecycle/query.d.ts +45 -4
- package/dist/lifecycle/query.d.ts.map +1 -1
- package/dist/lifecycle/query.js +89 -7
- package/dist/lifecycle/replay.d.ts +10 -3
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/replay.js +10 -6
- package/dist/lifecycle/scenario.d.ts +4 -3
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
- package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-fake.js +21 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-s3.js +254 -0
- package/dist/lifecycle/snapshot-store.d.ts +170 -0
- package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store.js +832 -0
- package/dist/lifecycle/snapshot.d.ts +11 -0
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +18 -5
- package/dist/lifecycle/type-listing.d.ts +148 -0
- package/dist/lifecycle/type-listing.d.ts.map +1 -0
- package/dist/lifecycle/type-listing.js +166 -0
- package/dist/lint/component-checks.d.ts +15 -1
- package/dist/lint/component-checks.d.ts.map +1 -1
- package/dist/lint/component-checks.js +1 -0
- package/dist/lint/presets/relaxed.json +2 -1
- package/dist/lint/presets/strict.json +2 -1
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
- package/dist/lint/rules/comp/index.d.ts +2 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/comp/index.js +4 -1
- package/dist/observation.d.ts +19 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +18 -1
- package/dist/op/activities/activity-contracts.d.ts +1 -1
- package/dist/op/activities/activity-contracts.js +1 -1
- package/dist/op/activities/change-set.d.ts +6 -4
- package/dist/op/activities/change-set.d.ts.map +1 -1
- package/dist/op/activities/change-set.js +4 -2
- package/dist/op/activity-failure.d.ts +27 -0
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-failure.js +31 -0
- package/dist/op/index.d.ts +2 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +5 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -8
- package/dist/pr-loop.d.ts +56 -1
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +51 -1
- package/dist/pulumi-urn.d.ts +101 -0
- package/dist/pulumi-urn.d.ts.map +1 -0
- package/dist/pulumi-urn.js +131 -0
- package/dist/release-floors.d.ts +23 -0
- package/dist/release-floors.d.ts.map +1 -0
- package/dist/release-floors.js +22 -0
- package/dist/sensitive-key.d.ts +23 -0
- package/dist/sensitive-key.d.ts.map +1 -0
- package/dist/sensitive-key.js +31 -0
- package/dist/wave-runtime.mjs +234 -234
- package/dist/workspace/adoption-report.d.ts +53 -0
- package/dist/workspace/adoption-report.d.ts.map +1 -0
- package/dist/workspace/adoption-report.js +76 -0
- package/dist/workspace/capabilities-contract.d.ts +7 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -1
- package/dist/workspace/capabilities-contract.js +2 -1
- package/dist/workspace/capabilities.schema.json +98 -0
- package/dist/workspace/change-set.schema.json +42 -3
- package/dist/workspace/ci-cloud-identity.d.ts +13 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +30 -1
- package/dist/workspace/compose-graph.d.ts +10 -1
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +6 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +6 -1
- package/dist/workspace/drift-cli.d.ts +81 -0
- package/dist/workspace/drift-cli.d.ts.map +1 -0
- package/dist/workspace/drift-cli.js +317 -0
- package/dist/workspace/drift.schema.json +672 -0
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +3 -0
- package/dist/workspace/graph.schema.json +72 -0
- package/dist/workspace/init.d.ts +5 -1
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/init.js +55 -9
- package/dist/workspace/ledger.schema.json +1 -1
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +2 -0
- package/dist/workspace/pr-report.schema.json +51 -2
- package/dist/workspace/promote-waves.d.ts +112 -0
- package/dist/workspace/promote-waves.d.ts.map +1 -0
- package/dist/workspace/promote-waves.js +290 -0
- package/dist/workspace/promote.d.ts +84 -2
- package/dist/workspace/promote.d.ts.map +1 -1
- package/dist/workspace/promote.js +347 -61
- package/dist/workspace/reason-codes.d.ts +12 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +14 -0
- package/dist/workspace/search-contract.d.ts +3 -3
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/search-contract.js +2 -0
- package/dist/workspace/search.schema.json +103 -5
- package/package.json +14 -1
- package/src/__fixtures__/drift-record/README.md +58 -0
- package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
- package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
- package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
- package/src/__fixtures__/drift-record/component-status.json +91 -0
- package/src/__fixtures__/drift-record/deep-diff.json +259 -0
- package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
- package/src/__fixtures__/drift-record/live-diff.json +125 -0
- package/src/audit/discover.e2e.test.ts +14 -0
- package/src/audit/discover.ts +3 -1
- package/src/behaviour.ts +1 -1
- package/src/capabilities/templates.json +145 -0
- package/src/capabilities/templates.test.ts +212 -0
- package/src/capabilities/templates.ts +204 -0
- package/src/change-set-bundle.test.ts +83 -0
- package/src/change-set-graph.ts +80 -9
- package/src/change-set.test.ts +24 -0
- package/src/change-set.ts +77 -14
- package/src/ci-identity-targets.test.ts +23 -0
- package/src/ci-identity.ts +10 -4
- package/src/cli/commands/import-index.test.ts +117 -0
- package/src/cli/commands/import-index.ts +265 -0
- package/src/cli/commands/lint.ts +4 -2
- package/src/cli/handlers/components.test.ts +9 -0
- package/src/cli/handlers/components.ts +12 -2
- package/src/cli/handlers/fan-out.ts +17 -3
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +26 -4
- package/src/cli/handlers/index-cloud.ts +32 -0
- package/src/cli/handlers/index-diff.test.ts +68 -0
- package/src/cli/handlers/index-diff.ts +146 -0
- package/src/cli/handlers/ledger-root-member.test.ts +161 -0
- package/src/cli/handlers/lifecycle-drift.ts +68 -0
- package/src/cli/handlers/lifecycle.test.ts +33 -11
- package/src/cli/handlers/lifecycle.ts +205 -30
- package/src/cli/handlers/misc.ts +73 -0
- package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
- package/src/cli/handlers/pr.ts +14 -2
- package/src/cli/handlers/promote.test.ts +64 -0
- package/src/cli/handlers/promote.ts +81 -6
- package/src/cli/handlers/query.ts +27 -4
- package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
- package/src/cli/handlers/run-groups-cli.test.ts +229 -0
- package/src/cli/handlers/scenario.test.ts +13 -0
- package/src/cli/handlers/scenario.ts +8 -7
- package/src/cli/handlers/search-index.test.ts +255 -0
- package/src/cli/handlers/search-json.test.ts +1 -1
- package/src/cli/handlers/search-ownership.test.ts +194 -0
- package/src/cli/handlers/search.ts +271 -38
- package/src/cli/main.test.ts +7 -0
- package/src/cli/main.ts +56 -1
- package/src/cli/mcp/lifecycle-tools.ts +3 -2
- package/src/cli/mcp/resource-handlers.ts +4 -3
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/cli/parse-args.ts +40 -2
- package/src/cli/registry.ts +19 -1
- package/src/cloud-lister/aws.test.ts +202 -0
- package/src/cloud-lister/aws.ts +226 -0
- package/src/cloud-lister/azure.test.ts +132 -0
- package/src/cloud-lister/azure.ts +142 -0
- package/src/cloud-lister/common.ts +115 -0
- package/src/cloud-lister/credentials.ts +110 -0
- package/src/cloud-lister/fakes.ts +66 -0
- package/src/cloud-lister/gcp.test.ts +121 -0
- package/src/cloud-lister/gcp.ts +159 -0
- package/src/cloud-lister/guard.test.ts +198 -0
- package/src/cloud-lister/guard.ts +111 -0
- package/src/cloud-lister/ids.ts +54 -0
- package/src/cloud-lister/index.ts +134 -0
- package/src/cloud-lister/kubernetes.test.ts +160 -0
- package/src/cloud-lister/kubernetes.ts +284 -0
- package/src/cloud-lister/markers.ts +74 -0
- package/src/cloud-lister/mask.ts +77 -0
- package/src/cloud-lister/shape.test.ts +81 -0
- package/src/cloud-lister/sigv4.ts +70 -0
- package/src/cloud-lister/transport.ts +118 -0
- package/src/cloud-lister/types.ts +135 -0
- package/src/components/capability-plugin-loader.ts +4 -0
- package/src/components/capability-plugin.ts +10 -0
- package/src/components/capability.ts +229 -0
- package/src/components/cli-support.ts +70 -14
- package/src/components/declared-reads.test.ts +103 -0
- package/src/components/declared-reads.ts +134 -0
- package/src/components/deploy-units.ts +4 -0
- package/src/components/driver.ts +89 -4
- package/src/components/fan-out-run.ts +95 -15
- package/src/components/fan-out-support.ts +10 -1
- package/src/components/fan-out.ts +30 -0
- package/src/components/groups.test.ts +320 -0
- package/src/components/groups.ts +880 -0
- package/src/components/index.ts +42 -0
- package/src/components/plan-shape-conformance.ts +130 -0
- package/src/components/pr-env-run-bundle.test.ts +2 -0
- package/src/components/pr-run.test.ts +516 -3
- package/src/components/pr-run.ts +231 -41
- package/src/components/promote-rounds.ts +25 -2
- package/src/components/promote.ts +55 -5
- package/src/components/registry.test.ts +1 -1
- package/src/components/release-gate.ts +115 -30
- package/src/components/release-groups.test.ts +362 -0
- package/src/components/run-groups.ts +113 -0
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +1 -0
- package/src/components/verbs/member-outputs.test.ts +205 -0
- package/src/components/verbs/member-outputs.ts +308 -0
- package/src/config.ts +44 -0
- package/src/deep-observation.ts +5 -23
- package/src/drift-record.test.ts +596 -0
- package/src/drift-record.ts +1206 -0
- package/src/engine-facts.guard.test.ts +111 -0
- package/src/engine-facts.test.ts +178 -0
- package/src/engine-facts.ts +154 -0
- package/src/gated-waves-bundle.test.ts +2 -0
- package/src/graph-ir-live.test.ts +92 -0
- package/src/graph-ir.ts +317 -7
- package/src/graph-refs.ts +416 -0
- package/src/graph-schema/fixtures/adopt.json +15 -0
- package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/src/graph-schema/fixtures/index.json +68 -0
- package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/src/graph-schema/fixtures.ts +267 -0
- package/src/graph-schema/generate.ts +263 -0
- package/src/graph-schema/graph-schema.test.ts +153 -0
- package/src/graph-schema/graph.schema.json +1387 -0
- package/src/health.schema.json +2 -1
- package/src/identity.ts +2 -0
- package/src/index.ts +2 -0
- package/src/instance-nodes.ts +288 -0
- package/src/lexicon.ts +76 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
- package/src/lifecycle/adoption-file.test.ts +100 -0
- package/src/lifecycle/adoption-file.ts +154 -0
- package/src/lifecycle/cloud-index.test.ts +517 -0
- package/src/lifecycle/cloud-index.ts +736 -0
- package/src/lifecycle/derive-edges.test.ts +193 -0
- package/src/lifecycle/derive-edges.ts +182 -0
- package/src/lifecycle/git.ts +10 -4
- package/src/lifecycle/identity-match.test.ts +308 -0
- package/src/lifecycle/identity-match.ts +257 -0
- package/src/lifecycle/index-diff.test.ts +198 -0
- package/src/lifecycle/index-diff.ts +476 -0
- package/src/lifecycle/index-document.test.ts +64 -0
- package/src/lifecycle/index-document.ts +299 -0
- package/src/lifecycle/index-drift.test.ts +191 -0
- package/src/lifecycle/index-drift.ts +304 -0
- package/src/lifecycle/index-import.test.ts +255 -0
- package/src/lifecycle/index-import.ts +634 -0
- package/src/lifecycle/index-join.test.ts +250 -0
- package/src/lifecycle/index-join.ts +304 -0
- package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/member-ledger.test.ts +25 -7
- package/src/lifecycle/observation-baseline.test.ts +72 -1
- package/src/lifecycle/observation-baseline.ts +190 -27
- package/src/lifecycle/observe.ts +6 -1
- package/src/lifecycle/query.test.ts +69 -1
- package/src/lifecycle/query.ts +103 -10
- package/src/lifecycle/replay.test.ts +9 -5
- package/src/lifecycle/replay.ts +9 -5
- package/src/lifecycle/scenario.ts +4 -3
- package/src/lifecycle/snapshot-store-fake.ts +29 -0
- package/src/lifecycle/snapshot-store-s3.ts +316 -0
- package/src/lifecycle/snapshot-store.test.ts +385 -0
- package/src/lifecycle/snapshot-store.ts +981 -0
- package/src/lifecycle/snapshot.test.ts +13 -0
- package/src/lifecycle/snapshot.ts +23 -6
- package/src/lifecycle/type-listing.test.ts +166 -0
- package/src/lifecycle/type-listing.ts +309 -0
- package/src/lint/component-checks.ts +17 -1
- package/src/lint/config.test.ts +3 -0
- package/src/lint/presets/relaxed.json +2 -1
- package/src/lint/presets/strict.json +2 -1
- package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
- package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
- package/src/lint/rules/comp/comp.test.ts +2 -1
- package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
- package/src/lint/rules/comp/comp008.test.ts +77 -0
- package/src/lint/rules/comp/index.ts +4 -1
- package/src/observation-edges.test.ts +33 -0
- package/src/observation.ts +36 -1
- package/src/op/activities/activity-contracts.ts +1 -1
- package/src/op/activities/change-set.ts +8 -5
- package/src/op/activity-failure.ts +36 -0
- package/src/op/index.ts +2 -1
- package/src/op/local-executor.test.ts +80 -0
- package/src/op/local-executor.ts +27 -9
- package/src/pr-loop-bundle.test.ts +5 -2
- package/src/pr-loop.test.ts +43 -0
- package/src/pr-loop.ts +111 -2
- package/src/pulumi-urn.ts +151 -0
- package/src/release-floors.ts +22 -0
- package/src/sensitive-key.ts +34 -0
- package/src/workspace/adoption-report.test.ts +61 -0
- package/src/workspace/adoption-report.ts +103 -0
- package/src/workspace/capabilities-contract.test.ts +4 -0
- package/src/workspace/capabilities-contract.ts +9 -1
- package/src/workspace/capabilities.schema.json +98 -0
- package/src/workspace/change-set.schema.json +42 -3
- package/src/workspace/ci-cloud-identity.ts +31 -1
- package/src/workspace/compose-graph.test.ts +12 -0
- package/src/workspace/compose-graph.ts +12 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.ts +6 -1
- package/src/workspace/drift-cli.test.ts +65 -0
- package/src/workspace/drift-cli.ts +365 -0
- package/src/workspace/drift.schema.json +672 -0
- package/src/workspace/graph-cli.ts +5 -0
- package/src/workspace/graph.schema.json +72 -0
- package/src/workspace/init.test.ts +38 -1
- package/src/workspace/init.ts +50 -8
- package/src/workspace/intent-gaps.test.ts +18 -2
- package/src/workspace/ledger.schema.json +1 -1
- package/src/workspace/nested-graph.ts +1 -0
- package/src/workspace/pr-report.schema.json +51 -2
- package/src/workspace/promote-waves.test.ts +296 -0
- package/src/workspace/promote-waves.ts +340 -0
- package/src/workspace/promote.test.ts +158 -4
- package/src/workspace/promote.ts +398 -57
- package/src/workspace/reason-codes.test.ts +4 -1
- package/src/workspace/reason-codes.ts +14 -0
- package/src/workspace/search-contract.ts +2 -0
- package/src/workspace/search.schema.json +103 -5
package/src/components/pr-run.ts
CHANGED
|
@@ -67,6 +67,14 @@
|
|
|
67
67
|
* says whether the approved change set covers what it planned, by the same
|
|
68
68
|
* test a resume uses ({@link planCoveredBy}). Covered, the wave applies under
|
|
69
69
|
* the pull request's approval; not covered, it needs its own.
|
|
70
|
+
*
|
|
71
|
+
* ## Groups
|
|
72
|
+
*
|
|
73
|
+
* A fan-out with `groups` (ws-107, `./groups.ts`) plans each group with one
|
|
74
|
+
* `planGroup` call at its wave and applies it with one `runGroup` call
|
|
75
|
+
* there. A member's read of another member of its group is computed by that
|
|
76
|
+
* apply: it is no `unknownInputs` entry and holds nothing back. Each member's
|
|
77
|
+
* digest binds the group's, so the one approval binds the composition.
|
|
70
78
|
*/
|
|
71
79
|
|
|
72
80
|
import { composeChangeSet, verifyChangeSetDigest, type ChangeSetDocument, type ChangeSetEntry, type ChangeSetPart } from "../change-set";
|
|
@@ -78,6 +86,7 @@ import {
|
|
|
78
86
|
PR_REPORT_CONTRACT,
|
|
79
87
|
PR_REPORT_SCHEMA_ID,
|
|
80
88
|
type PrApproval,
|
|
89
|
+
type PrGroup,
|
|
81
90
|
type PrMember,
|
|
82
91
|
type PrMemberRead,
|
|
83
92
|
type PrRefusal,
|
|
@@ -96,13 +105,14 @@ import {
|
|
|
96
105
|
} from "./driver";
|
|
97
106
|
import { deployUnits } from "./deploy-units";
|
|
98
107
|
import { runFanOut, type FanOutRunResult } from "./fan-out-run";
|
|
99
|
-
import { remainingFanOut, type ComponentChangeSignal, type FanOutPlan } from "./fan-out";
|
|
108
|
+
import { remainingFanOut, type ComponentChangeSignal, type FanOutGroup, type FanOutGroupFallback, type FanOutPlan } from "./fan-out";
|
|
100
109
|
import { coveredRound, type PlanShape, type RoundCoverage } from "./covered-round";
|
|
101
110
|
import type { PrApplyRecord } from "./fan-out-record";
|
|
102
|
-
import type { CapabilityRegistry, PlannedOutputs } from "./capability";
|
|
111
|
+
import type { CapabilityRegistry, GroupRun, PlannedOutputs } from "./capability";
|
|
112
|
+
import { groupsByComponent, narrowGroups, planComponentGroup, regroupRemaining } from "./groups";
|
|
103
113
|
import type { RunProgressEvent } from "./run-progress";
|
|
104
114
|
import type { RunEventSink } from "../run-event-stream";
|
|
105
|
-
import { capabilitySteps, planWaveComponent, readDeployedOutputs } from "./wave-plan";
|
|
115
|
+
import { capabilitySteps, planWaveComponent, readDeployedOutputs, type WaveComponentPlan } from "./wave-plan";
|
|
106
116
|
import type { PrMigrationFile } from "../pr-migrations";
|
|
107
117
|
|
|
108
118
|
export interface PrPlanSetOptions {
|
|
@@ -151,10 +161,24 @@ export interface PrPlanSet {
|
|
|
151
161
|
plannedOutputs?: Record<string, string[]>;
|
|
152
162
|
/** The shapes lexicons supplied with their plans (#4419), by member. */
|
|
153
163
|
shapes?: Record<string, PlanShape>;
|
|
164
|
+
/** The groups planned as one (ws-107), in plan order of their first member. Absent when none. */
|
|
165
|
+
groups?: PrPlanGroup[];
|
|
166
|
+
/** The components that could have grouped and did not, with why (ws-107, ruling 1): the fan-out's `fallbacks`. */
|
|
167
|
+
fallbacks?: FanOutGroupFallback[];
|
|
154
168
|
/** Whether any component failed to plan. */
|
|
155
169
|
failed: boolean;
|
|
156
170
|
}
|
|
157
171
|
|
|
172
|
+
/** A group a pull request planned as one (ws-107). */
|
|
173
|
+
export interface PrPlanGroup extends FanOutGroup {
|
|
174
|
+
/** The group digest folded into each member's `planDigest`; `null` when the group failed to plan. */
|
|
175
|
+
digest: string | null;
|
|
176
|
+
/** Why it failed to plan. Every member is then plan-failed with it. */
|
|
177
|
+
error?: string;
|
|
178
|
+
/** What `runGroup` gets back. Like `plans`, it lives in the process that planned. */
|
|
179
|
+
run?: GroupRun;
|
|
180
|
+
}
|
|
181
|
+
|
|
158
182
|
/** A failed component's members: the units its steps target, else the component itself. */
|
|
159
183
|
function failedParts(component: DriverComponent, error: string): ChangeSetPart[] {
|
|
160
184
|
const units = deployUnits(component.deploy).map((u) => u.unit);
|
|
@@ -190,14 +214,19 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
190
214
|
let failed = false;
|
|
191
215
|
const emit = options.onRunEvent;
|
|
192
216
|
emit?.({ type: "phase-start", component: null, phase: PR_PLAN_PHASE });
|
|
217
|
+
// ws-107: each group plans once, in its wave, with one call.
|
|
218
|
+
const groupOf = groupsByComponent(options.plan);
|
|
219
|
+
const groups: PrPlanGroup[] = [];
|
|
193
220
|
|
|
194
221
|
for (const wave of options.plan.waves) {
|
|
195
222
|
// The dependencies' outputs first, each once. One the change plans is
|
|
196
223
|
// read from its plan, which an earlier wave made (#3999). Any other is
|
|
197
224
|
// read from the deployed estate (#4104). A dependency that cannot say
|
|
198
225
|
// leaves its references unresolved, which its dependent's plan reports
|
|
199
|
-
// if it needs them.
|
|
200
|
-
const deps = [
|
|
226
|
+
// if it needs them. A member of the dependent's own group plans with it.
|
|
227
|
+
const deps = [
|
|
228
|
+
...new Set(wave.flatMap((name) => (byName.get(name)?.dependsOn ?? []).filter((dep) => !groupOf.get(name)?.components.includes(dep)))),
|
|
229
|
+
].filter((dep) => !read.has(dep));
|
|
201
230
|
const fromEstate: string[] = [];
|
|
202
231
|
for (const dep of deps) {
|
|
203
232
|
read.add(dep);
|
|
@@ -211,27 +240,59 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
211
240
|
planned[dep] = Object.keys(known).sort();
|
|
212
241
|
}
|
|
213
242
|
Object.assign(deployed, await readDeployedOutputs(fromEstate, byName, options.registry, (dep) => deployContext(options, dep), outputs));
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
243
|
+
const settle = (name: string, start: number, p: WaveComponentPlan): void => {
|
|
244
|
+
emit?.({
|
|
245
|
+
type: "step-settle",
|
|
246
|
+
component: name,
|
|
247
|
+
phase: PR_PLAN_PHASE,
|
|
248
|
+
step: "plan",
|
|
249
|
+
status: "error" in p ? "failed" : "ok",
|
|
250
|
+
durationMs: Date.now() - start,
|
|
251
|
+
...("error" in p ? { error: p.error } : {}),
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
const groupsHere = [...new Set(wave.map((name) => groupOf.get(name)).filter((g): g is FanOutGroup => g !== undefined))];
|
|
255
|
+
const results = (
|
|
256
|
+
await Promise.all([
|
|
257
|
+
...wave
|
|
258
|
+
.filter((name) => !groupOf.has(name))
|
|
259
|
+
.map(async (name) => {
|
|
260
|
+
emit?.({ type: "step-start", component: name, phase: PR_PLAN_PHASE, step: "plan" });
|
|
261
|
+
const start = Date.now();
|
|
262
|
+
const p = await planWaveComponent(byName.get(name)!, options.registry, deployContext(options, name), outputs);
|
|
263
|
+
settle(name, start, p);
|
|
264
|
+
return [p];
|
|
265
|
+
}),
|
|
266
|
+
...groupsHere.map(async (group) => {
|
|
267
|
+
for (const name of group.components) emit?.({ type: "step-start", component: name, phase: PR_PLAN_PHASE, step: "plan" });
|
|
268
|
+
const start = Date.now();
|
|
269
|
+
const g = await planComponentGroup({
|
|
270
|
+
group,
|
|
271
|
+
components: byName,
|
|
272
|
+
registry: options.registry,
|
|
273
|
+
env: options.env,
|
|
274
|
+
...(options.vars ? { vars: options.vars } : {}),
|
|
275
|
+
ctxOf: (name) => deployContext(options, name),
|
|
276
|
+
outputs,
|
|
277
|
+
});
|
|
278
|
+
for (const p of g.plans) settle(p.component, start, p);
|
|
279
|
+
const error = g.plans.find((p): p is Extract<WaveComponentPlan, { error: string }> => "error" in p)?.error;
|
|
280
|
+
groups.push({ ...group, digest: g.digest ?? null, ...(error !== undefined ? { error } : {}), ...(g.run ? { run: g.run } : {}) });
|
|
281
|
+
return g.plans;
|
|
282
|
+
}),
|
|
283
|
+
])
|
|
284
|
+
).flat();
|
|
285
|
+
/** What each member of a group planned to expose, for its group-mates' reads. */
|
|
286
|
+
const plannedHere = new Map(results.map((p) => [p.component, "error" in p ? undefined : p.outputs?.known]));
|
|
230
287
|
for (const p of results) {
|
|
231
288
|
const component = byName.get(p.component)!;
|
|
232
|
-
const
|
|
289
|
+
const group = groupOf.get(p.component);
|
|
290
|
+
const mates = group ? new Set(group.components) : undefined;
|
|
291
|
+
const unknown = unknownInputsOf(component, outputs, inChange, mates);
|
|
233
292
|
if (unknown.unknown.length > 0 || unknown.build.length > 0) unknownOf.set(p.component, unknown);
|
|
234
|
-
|
|
293
|
+
// A read of a group-mate is known when that mate's plan knows it.
|
|
294
|
+
const readsView = group ? { ...outputs, ...Object.fromEntries(group.components.filter((c) => c !== p.component).map((c) => [c, { ...(plannedHere.get(c) ?? {}) }])) } : outputs;
|
|
295
|
+
const reads = readsOf(component, readsView);
|
|
235
296
|
if (reads.length > 0) readsBy.set(p.component, reads);
|
|
236
297
|
if ("error" in p) {
|
|
237
298
|
failed = true;
|
|
@@ -273,12 +334,16 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
273
334
|
const sorted = (r: Record<string, string[]>) => Object.fromEntries(Object.entries(r).sort(([a], [b]) => a.localeCompare(b)));
|
|
274
335
|
const deployedOutputs = sorted(deployed);
|
|
275
336
|
const plannedOutputs = sorted(planned);
|
|
337
|
+
const at = new Map(options.plan.order.map((name, i) => [name, i]));
|
|
338
|
+
groups.sort((a, b) => (at.get(a.components[0]!) ?? 0) - (at.get(b.components[0]!) ?? 0));
|
|
276
339
|
return {
|
|
277
340
|
doc,
|
|
278
341
|
members,
|
|
279
342
|
plans,
|
|
280
343
|
outputs,
|
|
281
344
|
failed,
|
|
345
|
+
...(groups.length > 0 ? { groups } : {}),
|
|
346
|
+
...(options.plan.fallbacks?.length ? { fallbacks: options.plan.fallbacks } : {}),
|
|
282
347
|
...(Object.keys(shapes).length > 0 ? { shapes } : {}),
|
|
283
348
|
...(Object.keys(deployedOutputs).length > 0 ? { deployedOutputs } : {}),
|
|
284
349
|
...(Object.keys(plannedOutputs).length > 0 ? { plannedOutputs } : {}),
|
|
@@ -291,18 +356,21 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
291
356
|
* that upstream applies, each sorted. `build` are the published artifact
|
|
292
357
|
* identities a build in the change produces (`WiringReference.publish`):
|
|
293
358
|
* the build's provenance binds those, so they are never approved again.
|
|
294
|
-
* `unknown` are the rest.
|
|
359
|
+
* `unknown` are the rest. A reference to a member of `group`, the
|
|
360
|
+
* component's own group (ws-107), is neither: the group's apply computes it.
|
|
295
361
|
*/
|
|
296
362
|
function unknownInputsOf(
|
|
297
363
|
component: DriverComponent,
|
|
298
364
|
outputs: Record<string, Record<string, unknown>>,
|
|
299
365
|
inChange: ReadonlySet<string>,
|
|
366
|
+
group?: ReadonlySet<string>,
|
|
300
367
|
): { unknown: string[]; build: string[] } {
|
|
301
368
|
const unknown = new Set<string>();
|
|
302
369
|
const build = new Set<string>();
|
|
303
370
|
for (const step of capabilitySteps(component.deploy)) {
|
|
304
371
|
const { kind: _kind, ...rest } = step;
|
|
305
372
|
for (const r of unresolvedWiringReferences(rest, outputs)) {
|
|
373
|
+
if (group?.has(r.source)) continue;
|
|
306
374
|
if (inChange.has(r.source)) (r.publish ? build : unknown).add(r.ref);
|
|
307
375
|
}
|
|
308
376
|
}
|
|
@@ -339,18 +407,21 @@ function readsOf(component: DriverComponent, outputs: Record<string, Record<stri
|
|
|
339
407
|
* stands (#3999): each reference its steps make that resolves to another
|
|
340
408
|
* value, said once. `undefined` when every one resolves as it did. A
|
|
341
409
|
* published artifact's identity from a build in `inChange` never holds a
|
|
342
|
-
* member back: the build's provenance binds it, not this approval.
|
|
410
|
+
* member back: the build's provenance binds it, not this approval. Nor
|
|
411
|
+
* does a reference to a member of `group`, the component's own group
|
|
412
|
+
* (ws-107), whose apply computes it.
|
|
343
413
|
*/
|
|
344
414
|
export function heldReason(
|
|
345
415
|
component: DriverComponent,
|
|
346
416
|
read: Record<string, Record<string, unknown>>,
|
|
347
417
|
now: Record<string, Record<string, unknown>>,
|
|
348
418
|
inChange: ReadonlySet<string> = new Set(),
|
|
419
|
+
group: ReadonlySet<string> = new Set(),
|
|
349
420
|
): string | undefined {
|
|
350
421
|
const refs = new Set<string>();
|
|
351
422
|
for (const step of capabilitySteps(component.deploy)) {
|
|
352
423
|
const { kind: _kind, ...rest } = step;
|
|
353
|
-
for (const r of movedWiringReferences(rest, read, now)) if (!(r.publish && inChange.has(r.source))) refs.add(r.ref);
|
|
424
|
+
for (const r of movedWiringReferences(rest, read, now)) if (!(r.publish && inChange.has(r.source)) && !group.has(r.source)) refs.add(r.ref);
|
|
354
425
|
}
|
|
355
426
|
if (refs.size === 0) return undefined;
|
|
356
427
|
return `its plan read ${[...refs].sort().join(", ")} before ${refs.size === 1 ? "that output" : "those outputs"} applied as another value`;
|
|
@@ -494,6 +565,9 @@ function roundCover(
|
|
|
494
565
|
byName: ReadonlyMap<string, DriverComponent>,
|
|
495
566
|
): RoundCoverage {
|
|
496
567
|
if (options.approval?.status !== "approved") return { covered: false, reason: "no approval of the first plan stands" };
|
|
568
|
+
// ws-107: a round's group is covered only when it has the approved group's members.
|
|
569
|
+
const moved = groupsMoved(options.set.groups ?? [], fresh.groups ?? [], plan.order);
|
|
570
|
+
if (moved) return { covered: false, reason: moved };
|
|
497
571
|
// The values the round reads of a component it plans again are the ones
|
|
498
572
|
// that plan says it will expose: it has not applied yet, so the outputs
|
|
499
573
|
// standing say nothing of it. An output the first plan knew and the round's
|
|
@@ -512,16 +586,45 @@ function roundCover(
|
|
|
512
586
|
}
|
|
513
587
|
changedKnown[name] = [...refs].sort();
|
|
514
588
|
}
|
|
589
|
+
// A member whose round plan still reads only outputs of components this
|
|
590
|
+
// round plans and has not applied (a chain of imports, #4466d) cannot apply
|
|
591
|
+
// in this round: it is held again once those apply, and its own round
|
|
592
|
+
// checks its coverage. Only the members this round can apply are checked.
|
|
593
|
+
const inRound = plan.order;
|
|
594
|
+
const fromRound = (ref: string): boolean => inRound.some((c) => ref === c || ref.startsWith(`${c}.`));
|
|
595
|
+
const waits = (m: { unknownInputs?: string[] }): boolean => (m.unknownInputs?.length ?? 0) > 0 && m.unknownInputs!.every(fromRound);
|
|
515
596
|
const approved = { doc: options.set.doc, members: options.set.members, ...(options.set.shapes ? { shapes: options.set.shapes } : {}) };
|
|
516
597
|
return coveredRound({
|
|
517
598
|
approved,
|
|
518
599
|
approvedDigest: options.set.doc.digest,
|
|
519
|
-
fresh: { doc: fresh.doc, members: fresh.members, ...(fresh.shapes ? { shapes: fresh.shapes } : {}) },
|
|
600
|
+
fresh: { doc: fresh.doc, members: fresh.members.filter((m) => !waits(m)), ...(fresh.shapes ? { shapes: fresh.shapes } : {}) },
|
|
520
601
|
changedKnown,
|
|
521
602
|
attributes: (member) => memberAttributes(options.set.doc, fresh.doc, member, fresh.shapes?.[member] !== undefined),
|
|
522
603
|
});
|
|
523
604
|
}
|
|
524
605
|
|
|
606
|
+
/**
|
|
607
|
+
* Why the groups `fresh` planned are not the `approved` ones over the
|
|
608
|
+
* components in `order` (ws-107), or `undefined` when each group the round
|
|
609
|
+
* plans has the members the approved group had.
|
|
610
|
+
*/
|
|
611
|
+
function groupsMoved(approved: readonly FanOutGroup[], fresh: readonly FanOutGroup[], order: readonly string[]): string | undefined {
|
|
612
|
+
const same = (a: readonly string[], b: readonly string[]): boolean => a.length === b.length && a.every((c) => b.includes(c));
|
|
613
|
+
const inRound = new Set(order);
|
|
614
|
+
for (const was of approved) {
|
|
615
|
+
const here = was.components.filter((c) => inRound.has(c));
|
|
616
|
+
if (here.length === 0) continue;
|
|
617
|
+
const now = fresh.find((g) => g.components.includes(here[0]!));
|
|
618
|
+
if (!now || !same(now.components, was.components)) {
|
|
619
|
+
return `the approved plan grouped ${was.components.join(", ")}, and the round ${now ? `groups ${now.components.join(", ")}` : `plans ${here.join(", ")} without that group`}`;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
for (const now of fresh) {
|
|
623
|
+
if (!approved.some((was) => same(was.components, now.components))) return `the round groups ${now.components.join(", ")}, which the approved plan did not`;
|
|
624
|
+
}
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
|
|
525
628
|
/**
|
|
526
629
|
* The part of coverage that reads the change set's own attributes, shared by
|
|
527
630
|
* a round, a resume and a wave: a member whose planner describes no changes
|
|
@@ -553,6 +656,8 @@ function joinRuns(a: FanOutRunResult, b: FanOutRunResult): FanOutRunResult {
|
|
|
553
656
|
componentOutputs: b.componentOutputs,
|
|
554
657
|
...(gate ? { gate } : {}),
|
|
555
658
|
...(b.held ? { held: b.held } : {}),
|
|
659
|
+
...(a.notStarted || b.notStarted ? { notStarted: [...new Set([...(a.notStarted ?? []), ...(b.notStarted ?? [])])].sort() } : {}),
|
|
660
|
+
...(a.groupOutcomes || b.groupOutcomes ? { groupOutcomes: { ...(a.groupOutcomes ?? {}), ...(b.groupOutcomes ?? {}) } } : {}),
|
|
556
661
|
};
|
|
557
662
|
}
|
|
558
663
|
|
|
@@ -576,20 +681,25 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
|
|
|
576
681
|
...Object.fromEntries(Object.entries(options.set.outputs).filter(([name]) => !toApply.has(name))),
|
|
577
682
|
...(options.resumed?.outputs ?? {}),
|
|
578
683
|
};
|
|
579
|
-
const runRound = (plan: FanOutPlan,
|
|
580
|
-
|
|
684
|
+
const runRound = (plan: FanOutPlan, set: Pick<PrPlanSet, "plans" | "groups">, read: Record<string, Record<string, unknown>>, outputs: Record<string, Record<string, unknown>>) => {
|
|
685
|
+
// ws-107: each group runs the plan its group planned, and a read of a group-mate holds nothing back.
|
|
686
|
+
const groupOf = groupsByComponent(plan);
|
|
687
|
+
const groupRuns = Object.fromEntries((set.groups ?? []).filter((g) => g.run).map((g) => [g.components[0]!, g.run!]));
|
|
688
|
+
return runFanOut(plan, options.components, options.registry, {
|
|
581
689
|
env: options.env,
|
|
582
690
|
...(options.vars ? { vars: options.vars } : {}),
|
|
583
|
-
plans,
|
|
691
|
+
plans: set.plans,
|
|
692
|
+
...(Object.keys(groupRuns).length > 0 ? { groupRuns } : {}),
|
|
584
693
|
...(options.targets ? { targets: options.targets } : {}),
|
|
585
694
|
componentOutputs: outputs,
|
|
586
|
-
hold: (name, now) => heldReason(byName.get(name)!, read, now, inChange),
|
|
695
|
+
hold: (name, now) => heldReason(byName.get(name)!, read, now, inChange, new Set(groupOf.get(name)?.components ?? [])),
|
|
587
696
|
...(options.onProgress ? { onProgress: options.onProgress } : {}),
|
|
588
697
|
...(options.onRunEvent ? { onRunEvent: options.onRunEvent } : {}),
|
|
589
698
|
...(options.onComponentSettled ? { onComponentSettled: options.onComponentSettled } : {}),
|
|
590
699
|
});
|
|
700
|
+
};
|
|
591
701
|
|
|
592
|
-
let run = await runRound(options.plan, options.set
|
|
702
|
+
let run = await runRound(options.plan, options.set, options.set.outputs, { ...standing });
|
|
593
703
|
const rounds: PrRound[] = [];
|
|
594
704
|
/** Per component, the round that applied it or holds it, and that round's set, after the first. */
|
|
595
705
|
const roundOf = new Map<string, { round: number; set: PrPlanSet }>();
|
|
@@ -598,7 +708,8 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
|
|
|
598
708
|
const round = rounds.length + 2;
|
|
599
709
|
const gate = prRoundGate(options.gate ?? PR_APPLY_GATE, round);
|
|
600
710
|
const heldNames = new Set(run.held.map((h) => h.component));
|
|
601
|
-
|
|
711
|
+
// A held group is planned again as the same group (ws-107).
|
|
712
|
+
const plan = narrowGroups(remainingFanOut(options.plan, options.components, { completed: options.plan.order.filter((name) => !heldNames.has(name)) }), options.components, options.plan.groups);
|
|
602
713
|
const set = await planPrSet({
|
|
603
714
|
components: options.components,
|
|
604
715
|
plan,
|
|
@@ -638,25 +749,33 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
|
|
|
638
749
|
stopped = "waiting";
|
|
639
750
|
break;
|
|
640
751
|
}
|
|
641
|
-
const next = await runRound(plan, set
|
|
752
|
+
const next = await runRound(plan, set, set.outputs, run.componentOutputs);
|
|
642
753
|
record(next.status === "fail" ? "failed" : "applied", decision.approval, undefined, decision.covered);
|
|
643
754
|
run = joinRuns(run, next);
|
|
644
755
|
}
|
|
645
756
|
|
|
646
757
|
const completed = new Set(run.completed);
|
|
758
|
+
const notStarted = new Set(run.notStarted ?? []);
|
|
647
759
|
const held = new Map((run.held ?? []).map((h) => [h.component, h]));
|
|
648
760
|
const blocked = new Map(run.blocked.map((b) => [b.component, b.blockedBy]));
|
|
649
761
|
const errorOf = (component: string): string | undefined => {
|
|
650
762
|
const result = [...run.results].reverse().find((r) => r.component === component);
|
|
651
763
|
if (result?.status === "gated") return `stopped at its own gate ${result.gate?.gate ?? ""}`.trim();
|
|
652
|
-
return result?.records.find((r) => r.status === "fail")?.error;
|
|
764
|
+
return result?.records.find((r) => r.status === "fail" || (notStarted.has(component) && r.status === "skipped"))?.error;
|
|
653
765
|
};
|
|
654
766
|
const members = options.set.members.map((m): PrMember => {
|
|
655
767
|
if (priorCompleted.has(m.component)) return { ...m, status: "applied" };
|
|
656
768
|
const later = roundOf.get(m.component);
|
|
657
769
|
const fresh = later?.set.members.find((x) => x.member === m.member);
|
|
658
|
-
const
|
|
770
|
+
const planned: PrMember = later && fresh ? { ...m, planDigest: fresh.planDigest, counts: fresh.counts, round: later.round } : m;
|
|
771
|
+
// #4446: how its part of a group's apply ended, as the capability said.
|
|
772
|
+
const groupOutcome = run.groupOutcomes?.[m.component];
|
|
773
|
+
const asPlanned: PrMember = groupOutcome ? { ...planned, groupOutcome } : planned;
|
|
659
774
|
if (completed.has(m.component)) return { ...asPlanned, status: "applied" };
|
|
775
|
+
if (notStarted.has(m.component)) {
|
|
776
|
+
const error = errorOf(m.component);
|
|
777
|
+
return { ...asPlanned, status: "not-started", ...(error ? { error } : {}) };
|
|
778
|
+
}
|
|
660
779
|
const h = held.get(m.component);
|
|
661
780
|
if (h) return { ...asPlanned, status: "waiting", held: h.heldBy ? `${h.heldBy} is held: ${h.reason}` : h.reason };
|
|
662
781
|
if (fresh?.status === "plan-failed") return { ...asPlanned, status: "plan-failed", ...(fresh.error ? { error: fresh.error } : {}) };
|
|
@@ -763,6 +882,12 @@ export interface PrResumeOptions {
|
|
|
763
882
|
record: PrApplyRecord;
|
|
764
883
|
/** Components an earlier attempt applied. */
|
|
765
884
|
completed: string[];
|
|
885
|
+
/**
|
|
886
|
+
* The outputs the components in `completed` left (#4446). A member left
|
|
887
|
+
* of an approved group plans against the outputs its group-mates applied
|
|
888
|
+
* with, which the group's apply computed and the approved plan did not know.
|
|
889
|
+
*/
|
|
890
|
+
applied?: Record<string, Record<string, unknown>>;
|
|
766
891
|
}
|
|
767
892
|
|
|
768
893
|
export type PrResume =
|
|
@@ -796,20 +921,44 @@ export type PrResume =
|
|
|
796
921
|
* is `record.digest`, and the document gives that digest. The digest binds
|
|
797
922
|
* the entries, holes and side effects {@link planCoveredBy} reads (#3555),
|
|
798
923
|
* so a record cannot widen what a re-planned member may do.
|
|
924
|
+
*
|
|
925
|
+
* A group that failed part way (ws-107, "Resume"; #4446) leaves its
|
|
926
|
+
* `partial`, `failed` and `not-started` members; `applied` and `no-changes`
|
|
927
|
+
* ones are in `completed`. What is left of each approved group is grouped
|
|
928
|
+
* again ({@link regroupRemaining}), so it plans as a smaller group, or one
|
|
929
|
+
* by one when the rule or the capability says so. The smaller group's
|
|
930
|
+
* digest differs from the approved one, so coverage rests on entries and
|
|
931
|
+
* shapes, as for any resumed member.
|
|
799
932
|
*/
|
|
800
933
|
export async function resumePrSet(options: PrResumeOptions): Promise<PrResume> {
|
|
801
934
|
const { record } = options;
|
|
802
935
|
if (record.changeSet.digest !== record.digest || !verifyChangeSetDigest(record.changeSet)) {
|
|
803
936
|
return { ok: false, reasons: [`the record's change set is not the one its digest ${record.digest} names`] };
|
|
804
937
|
}
|
|
805
|
-
const plan =
|
|
938
|
+
const plan = await regroupRemaining({
|
|
939
|
+
components: options.components,
|
|
940
|
+
plan: remainingFanOut(options.plan, options.components, { completed: options.completed }),
|
|
941
|
+
registry: options.registry,
|
|
942
|
+
env: options.env,
|
|
943
|
+
...(options.vars ? { vars: options.vars } : {}),
|
|
944
|
+
groups: options.plan.groups,
|
|
945
|
+
...(options.plan.fallbacks ? { fallbacks: options.plan.fallbacks } : {}),
|
|
946
|
+
});
|
|
947
|
+
// The outputs of the applied members of a group with members left: the values the group's apply computed.
|
|
948
|
+
const left = new Set(plan.order);
|
|
949
|
+
const fromGroup = Object.fromEntries(
|
|
950
|
+
(options.plan.groups ?? [])
|
|
951
|
+
.filter((g) => g.components.some((c) => left.has(c)))
|
|
952
|
+
.flatMap((g) => g.components.filter((c) => !left.has(c) && options.applied?.[c] !== undefined))
|
|
953
|
+
.map((c) => [c, options.applied![c]!]),
|
|
954
|
+
);
|
|
806
955
|
const fresh = await planPrSet({
|
|
807
956
|
components: options.components,
|
|
808
957
|
plan,
|
|
809
958
|
registry: options.registry,
|
|
810
959
|
env: options.env,
|
|
811
960
|
...(options.vars ? { vars: options.vars } : {}),
|
|
812
|
-
seededOutputs: { ...(options.seededOutputs ?? {}), ...options.record.planOutputs },
|
|
961
|
+
seededOutputs: { ...(options.seededOutputs ?? {}), ...options.record.planOutputs, ...fromGroup },
|
|
813
962
|
...(options.targets ? { targets: options.targets } : {}),
|
|
814
963
|
});
|
|
815
964
|
// #4422: decided as a round decides (`coveredRound`), by the same function a wave uses.
|
|
@@ -823,7 +972,9 @@ export async function resumePrSet(options: PrResumeOptions): Promise<PrResume> {
|
|
|
823
972
|
doc: options.record.changeSet,
|
|
824
973
|
members: options.record.members.map((m) => ({ ...m, status: "planned" as const })),
|
|
825
974
|
plans: fresh.plans,
|
|
826
|
-
outputs: options.record.planOutputs,
|
|
975
|
+
outputs: { ...options.record.planOutputs, ...fromGroup },
|
|
976
|
+
...(fresh.groups ? { groups: fresh.groups } : {}),
|
|
977
|
+
...(fresh.fallbacks ? { fallbacks: fresh.fallbacks } : {}),
|
|
827
978
|
failed: false,
|
|
828
979
|
},
|
|
829
980
|
};
|
|
@@ -843,7 +994,17 @@ export function prWavePlan(plan: FanOutPlan, components: DriverComponent[], wave
|
|
|
843
994
|
const seeds = [
|
|
844
995
|
...new Set([...plan.seeds, ...order.flatMap((name) => (byName.get(name)?.dependsOn ?? []).filter((dep) => !inWave.has(dep)))]),
|
|
845
996
|
].sort();
|
|
846
|
-
|
|
997
|
+
// ws-107: a group sits in one wave, so what is left of it plans and applies as one there.
|
|
998
|
+
const groups = (plan.groups ?? []).map((g) => ({ ...g, components: g.components.filter((c) => inWave.has(c)) })).filter((g) => g.components.length > 1);
|
|
999
|
+
return {
|
|
1000
|
+
order,
|
|
1001
|
+
waves: order.length > 0 ? [order] : [],
|
|
1002
|
+
skipped: [],
|
|
1003
|
+
seeds,
|
|
1004
|
+
indeterminate: plan.indeterminate,
|
|
1005
|
+
digest: plan.digest,
|
|
1006
|
+
...(groups.length > 0 ? { groups } : {}),
|
|
1007
|
+
};
|
|
847
1008
|
}
|
|
848
1009
|
|
|
849
1010
|
/**
|
|
@@ -928,9 +1089,36 @@ export interface PrReportInput {
|
|
|
928
1089
|
migrations?: PrMigrationFile[];
|
|
929
1090
|
}
|
|
930
1091
|
|
|
1092
|
+
/**
|
|
1093
|
+
* The report's `groups` (#4446): each group the set planned, with the wave
|
|
1094
|
+
* of `waves` its members sit in and the reads of group-mates its members'
|
|
1095
|
+
* change-set parts name, which the group's apply computes.
|
|
1096
|
+
*/
|
|
1097
|
+
function reportGroups(set: PrPlanSet, waves: readonly string[][]): PrGroup[] {
|
|
1098
|
+
const componentOf = new Map(set.members.map((m) => [m.member, m.component]));
|
|
1099
|
+
return (set.groups ?? []).map((g) => {
|
|
1100
|
+
const wave = waves.findIndex((w) => g.components.some((c) => w.includes(c))) + 1;
|
|
1101
|
+
const reads = set.doc.members
|
|
1102
|
+
.filter((m) => g.components.includes(componentOf.get(m.member) ?? m.member))
|
|
1103
|
+
.flatMap((m) => (m.group?.reads ?? []).map((ref) => ({ component: componentOf.get(m.member) ?? m.member, ref })))
|
|
1104
|
+
.sort((a, b) => a.component.localeCompare(b.component) || a.ref.localeCompare(b.ref));
|
|
1105
|
+
return {
|
|
1106
|
+
capability: g.capability,
|
|
1107
|
+
components: [...g.components],
|
|
1108
|
+
wave: wave > 0 ? wave : 1,
|
|
1109
|
+
digest: g.digest,
|
|
1110
|
+
...(g.error !== undefined ? { error: g.error } : {}),
|
|
1111
|
+
...(reads.length > 0 ? { reads } : {}),
|
|
1112
|
+
};
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
|
|
931
1116
|
/** The report a stage writes. */
|
|
932
1117
|
export function prReport(input: PrReportInput): PrReport {
|
|
933
1118
|
const changed = new Set(input.signal.changed);
|
|
1119
|
+
const groups = reportGroups(input.set, input.plan.waves);
|
|
1120
|
+
const groupOf = new Map(groups.flatMap((g, i) => g.components.map((c) => [c, i] as const)));
|
|
1121
|
+
const members = (input.members ?? input.set.members).map((m) => (groupOf.has(m.component) ? { ...m, group: groupOf.get(m.component)! } : m));
|
|
934
1122
|
return {
|
|
935
1123
|
$schema: PR_REPORT_SCHEMA_ID,
|
|
936
1124
|
contract: PR_REPORT_CONTRACT,
|
|
@@ -961,7 +1149,9 @@ export function prReport(input: PrReportInput): PrReport {
|
|
|
961
1149
|
indeterminate: input.plan.indeterminate,
|
|
962
1150
|
waves: input.plan.waves,
|
|
963
1151
|
},
|
|
964
|
-
|
|
1152
|
+
...(groups.length > 0 ? { groups } : {}),
|
|
1153
|
+
...(input.set.fallbacks?.length ? { fallbacks: input.set.fallbacks.map((f) => ({ ...f })) } : {}),
|
|
1154
|
+
members,
|
|
965
1155
|
changeSet: input.set.doc,
|
|
966
1156
|
};
|
|
967
1157
|
}
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
* A stage whose components declare no gate has nowhere to wait, so the
|
|
23
23
|
* promote stops there with nothing more deployed.
|
|
24
24
|
*
|
|
25
|
+
* - **Groups** (ws-107, #4465). With `grouped`, the waves are the grouped
|
|
26
|
+
* fan-out's, so each group sits in one wave and is checked, planned again
|
|
27
|
+
* and run as one: when any member's input moved, every member left of the
|
|
28
|
+
* group is planned again with it. Under `--wave-gate` no group forms
|
|
29
|
+
* (`formRunGroups`), so there is none here.
|
|
30
|
+
*
|
|
25
31
|
* A promote that stops at a round's or a wave's gate writes a resume record
|
|
26
32
|
* ({@link PromoteResume}): the components that applied and the outputs they
|
|
27
33
|
* left. The same promote run again with it skips what applied and carries on
|
|
@@ -37,6 +43,8 @@ import { waveGateName } from "../gated-waves";
|
|
|
37
43
|
import { capabilitySteps } from "./wave-plan";
|
|
38
44
|
import type { ChangeSetDocument } from "../change-set";
|
|
39
45
|
import type { ReleasePlan } from "./release-gate";
|
|
46
|
+
import type { FanOutGroup, FanOutPlan } from "./fan-out";
|
|
47
|
+
import { groupsByComponent } from "./groups";
|
|
40
48
|
|
|
41
49
|
export const PROMOTE_RESUME_SCHEMA = "chant-promote-resume/v1";
|
|
42
50
|
|
|
@@ -90,6 +98,8 @@ export interface PromoteRoundsOptions {
|
|
|
90
98
|
replan: (only: ReadonlySet<string>, outputs: Record<string, Record<string, unknown>>) => Promise<ChangeSetDocument>;
|
|
91
99
|
/** Store a stage's plan and bind its gates (empty resolutions included). */
|
|
92
100
|
bind: (doc: ChangeSetDocument, gates: string[]) => Promise<ReleaseGateBinding>;
|
|
101
|
+
/** The grouped fan-out (#4465): its waves replace the ones `dependsOn` gives, and each group is checked as one. */
|
|
102
|
+
grouped?: FanOutPlan;
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
export interface PromoteRoundsResult {
|
|
@@ -169,7 +179,17 @@ function joinRuns(runs: DriverRunResult[], outputs: Record<string, Record<string
|
|
|
169
179
|
*/
|
|
170
180
|
export async function runPromotionInRounds(options: PromoteRoundsOptions): Promise<PromoteRoundsResult> {
|
|
171
181
|
const byName = new Map(options.components.map((c) => [c.name, c]));
|
|
172
|
-
const
|
|
182
|
+
const grouped = options.grouped?.groups?.length ? options.grouped : undefined;
|
|
183
|
+
const placed = new Set(grouped?.order ?? []);
|
|
184
|
+
// A grouped fan-out over what a resume left holds no wave for what applied earlier; that is skipped anyway.
|
|
185
|
+
const waves = grouped
|
|
186
|
+
? grouped.waves.map((w) => w.filter((name) => byName.has(name)))
|
|
187
|
+
: resolveComponentGraph(options.components.map((c) => ({ ...c, dependsOn: (c.dependsOn ?? []).filter((d) => byName.has(d)) }))).waves;
|
|
188
|
+
if (grouped) {
|
|
189
|
+
const missing = options.components.filter((c) => !placed.has(c.name) && !(options.completed ?? []).includes(c.name)).map((c) => c.name);
|
|
190
|
+
if (missing.length > 0) throw new Error(`the grouped waves leave out ${missing.join(", ")}, which this promote runs`);
|
|
191
|
+
}
|
|
192
|
+
const groupOf = grouped ? groupsByComponent(grouped) : new Map<string, FanOutGroup>();
|
|
173
193
|
const completed = [...(options.completed ?? [])];
|
|
174
194
|
const done = new Set(completed);
|
|
175
195
|
let outputs = { ...options.outputs };
|
|
@@ -181,9 +201,12 @@ export async function runPromotionInRounds(options: PromoteRoundsOptions): Promi
|
|
|
181
201
|
const waveNumber = index + 1;
|
|
182
202
|
const pending = wave.filter((name) => !done.has(name));
|
|
183
203
|
if (pending.length === 0) continue;
|
|
184
|
-
const
|
|
204
|
+
const moved = options.waveGate !== undefined && waveNumber > 1
|
|
185
205
|
? pending
|
|
186
206
|
: pending.filter((name) => movedInputs(byName.get(name)!, options.reads[name], outputs).length > 0);
|
|
207
|
+
// A group is planned again as one: a member whose input moved takes the rest of its group with it.
|
|
208
|
+
const withMates = new Set(moved.flatMap((name) => [name, ...(groupOf.get(name)?.components ?? [])]));
|
|
209
|
+
const check = pending.filter((name) => withMates.has(name));
|
|
187
210
|
const batches: Array<{ names: string[]; binding: ReleaseGateBinding; stage?: PromoteStage; rename?: (g: string) => string }> = [];
|
|
188
211
|
const rest = pending.filter((name) => !check.includes(name));
|
|
189
212
|
if (check.length > 0) {
|