@intentius/chant 0.124.51 → 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 +17 -3
- 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 +224 -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/covered-round.d.ts +173 -0
- package/dist/components/covered-round.d.ts.map +1 -0
- package/dist/components/covered-round.js +200 -0
- 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 +42 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +74 -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 +7 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +5 -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 +62 -7
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +354 -53
- 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/components/wave-plan.d.ts +2 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +4 -1
- 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 +92 -3
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +62 -3
- 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 +76 -3
- 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 +16 -3
- 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 +238 -0
- package/src/components/cli-support.ts +70 -14
- package/src/components/covered-round.test.ts +166 -0
- package/src/components/covered-round.ts +293 -0
- 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 +101 -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 +59 -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 +728 -4
- package/src/components/pr-run.ts +395 -55
- 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/components/wave-plan.ts +5 -2
- 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 +57 -1
- package/src/pr-loop.ts +154 -6
- 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 +76 -3
- 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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a capability finds in its steps' files (#4466): each becomes a
|
|
3
|
+
* `stackOutput()` reference in the reader's step and a `dependsOn` on the
|
|
4
|
+
* upstream, so it orders the fan-out and counts as a read.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "vitest";
|
|
8
|
+
import { CapabilityRegistry, type DeclaredRead, type DeclaredReadMember } from "./capability";
|
|
9
|
+
import { applyDeclaredReads, declaredReadsOf, withDeclaredReads } from "./declared-reads";
|
|
10
|
+
import { planFanOut } from "./fan-out";
|
|
11
|
+
import { unresolvedWiringReferences } from "./driver";
|
|
12
|
+
import { capabilitySteps } from "./wave-plan";
|
|
13
|
+
import type { DriverComponent } from "./driver";
|
|
14
|
+
|
|
15
|
+
const stack = (name: string, extra: Record<string, unknown> = {}, dependsOn: string[] = []): DriverComponent => ({
|
|
16
|
+
name,
|
|
17
|
+
dependsOn,
|
|
18
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "fake-stack", stack: name, ...extra }] }],
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function registry(reads: (members: readonly DeclaredReadMember[]) => DeclaredRead[]) {
|
|
22
|
+
const asked: DeclaredReadMember[][] = [];
|
|
23
|
+
const r = new CapabilityRegistry();
|
|
24
|
+
const unused = async (): Promise<never> => {
|
|
25
|
+
throw new Error("not called here");
|
|
26
|
+
};
|
|
27
|
+
r.register({
|
|
28
|
+
kind: "fake-stack",
|
|
29
|
+
run: unused,
|
|
30
|
+
declaredReads(_ctx: unknown, members: readonly DeclaredReadMember[]) {
|
|
31
|
+
asked.push([...members]);
|
|
32
|
+
return reads(members);
|
|
33
|
+
},
|
|
34
|
+
} as never);
|
|
35
|
+
r.register({ kind: "fake-plain", run: unused } as never);
|
|
36
|
+
return { registry: r, asked };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("declared reads (#4466)", () => {
|
|
40
|
+
test("a read becomes a stackOutput() reference at its place and a dependsOn, so the reader orders after the upstream", async () => {
|
|
41
|
+
const components = [stack("app"), stack("network")];
|
|
42
|
+
const { registry: r, asked } = registry(() => [{ reader: "app", upstream: "network", output: "VpcId", at: "/imports/network-VpcId" }]);
|
|
43
|
+
const out = await withDeclaredReads(components, r, "/project");
|
|
44
|
+
expect(asked[0]!.map((m) => m.component)).toEqual(["app", "network"]);
|
|
45
|
+
const app = out.find((c) => c.name === "app")!;
|
|
46
|
+
expect(app.dependsOn).toEqual(["network"]);
|
|
47
|
+
expect(capabilitySteps(app.deploy)[0]).toEqual({
|
|
48
|
+
kind: "fake-stack",
|
|
49
|
+
stack: "app",
|
|
50
|
+
imports: { "network-VpcId": { stackOutput: { stack: "network", name: "VpcId" } } },
|
|
51
|
+
});
|
|
52
|
+
// The read is now one core sees: no value for network's VpcId yet.
|
|
53
|
+
const { kind: _kind, ...rest } = capabilitySteps(app.deploy)[0]!;
|
|
54
|
+
expect(unresolvedWiringReferences(rest, {}).map((x) => x.ref)).toEqual(["network.VpcId"]);
|
|
55
|
+
// And it orders the fan-out.
|
|
56
|
+
const plan = planFanOut({ components: out, changed: ["app", "network"] });
|
|
57
|
+
expect(plan.waves).toEqual([["network"], ["app"]]);
|
|
58
|
+
// The upstream is untouched.
|
|
59
|
+
expect(out.find((c) => c.name === "network")).toBe(components[1]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("a place the declaration already fills is left as it is, and adds no dependsOn", () => {
|
|
63
|
+
const declared = stack("app", { imports: { "network-VpcId": "vpc-pinned" } });
|
|
64
|
+
const [out] = applyDeclaredReads([declared], [{ reader: "app", upstream: "network", output: "VpcId", at: "/imports/network-VpcId", index: 0, kind: "fake-stack" }]);
|
|
65
|
+
expect(out!.dependsOn).toEqual([]);
|
|
66
|
+
expect(capabilitySteps(out!.deploy)[0]).toMatchObject({ imports: { "network-VpcId": "vpc-pinned" } });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("a dependsOn already declared is not repeated", () => {
|
|
70
|
+
const declared = stack("app", {}, ["network"]);
|
|
71
|
+
const [out] = applyDeclaredReads([declared], [{ reader: "app", upstream: "network", output: "VpcId", at: "/imports/a", index: 0, kind: "fake-stack" }]);
|
|
72
|
+
expect(out!.dependsOn).toEqual(["network"]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("the read goes into the step of the reported kind and index, and an escaped pointer unescapes", () => {
|
|
76
|
+
const declared: DriverComponent = {
|
|
77
|
+
name: "app",
|
|
78
|
+
deploy: [
|
|
79
|
+
{ phase: "Build", steps: [{ kind: "fake-plain", note: "x" }] },
|
|
80
|
+
{ phase: "Apply", steps: [{ kind: "fake-stack", stack: "one" }, { kind: "fake-stack", stack: "two" }] },
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
const [out] = applyDeclaredReads([declared], [{ reader: "app", upstream: "network", output: "Arn", at: "/imports/a~1b~0c", index: 1, kind: "fake-stack" }]);
|
|
84
|
+
const steps = capabilitySteps(out!.deploy);
|
|
85
|
+
expect(steps[0]).toEqual({ kind: "fake-plain", note: "x" });
|
|
86
|
+
expect(steps[1]).toEqual({ kind: "fake-stack", stack: "one" });
|
|
87
|
+
expect(steps[2]).toEqual({ kind: "fake-stack", stack: "two", imports: { "a/b~c": { stackOutput: { stack: "network", name: "Arn" } } } });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("a read of itself, or of a component the project does not have, is dropped; a capability that throws reports none", async () => {
|
|
91
|
+
const components = [stack("app"), stack("network")];
|
|
92
|
+
const { registry: r } = registry(() => [
|
|
93
|
+
{ reader: "app", upstream: "app", output: "X", at: "/imports/x" },
|
|
94
|
+
{ reader: "app", upstream: "elsewhere", output: "X", at: "/imports/y" },
|
|
95
|
+
]);
|
|
96
|
+
expect(await declaredReadsOf(components, r, "/project")).toEqual([]);
|
|
97
|
+
const { registry: throwing } = registry(() => {
|
|
98
|
+
throw new Error("unreadable");
|
|
99
|
+
});
|
|
100
|
+
expect(await declaredReadsOf(components, throwing, "/project")).toEqual([]);
|
|
101
|
+
expect(await withDeclaredReads(components, throwing, "/project")).toEqual(components);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a capability finds in the files its steps name (#4466).
|
|
3
|
+
*
|
|
4
|
+
* A read core can see is a `stackOutput()` or `@<component>.publish.*`
|
|
5
|
+
* reference in a step. A CloudFormation template that imports another
|
|
6
|
+
* stack's export reads that stack too, but the read sits inside the
|
|
7
|
+
* template. A capability that can find such reads says so through
|
|
8
|
+
* `Capability.declaredReads`, and {@link withDeclaredReads} writes each one
|
|
9
|
+
* into the reader's step as the `stackOutput()` reference a person would have
|
|
10
|
+
* written, plus a `dependsOn` on the upstream. From there on it is an ordinary
|
|
11
|
+
* read: it orders the fan-out, it is named under `unknownInputs` while the
|
|
12
|
+
* upstream's plan cannot say the value, and a reader whose plan read another
|
|
13
|
+
* value than the upstream applied is held for a round.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { CapabilityRegistry, DeclaredRead, DeclaredReadMember } from "./capability";
|
|
17
|
+
import type { DriverComponent, DriverPhase, DriverStep } from "./driver";
|
|
18
|
+
import { capabilitySteps } from "./wave-plan";
|
|
19
|
+
|
|
20
|
+
/** A declared read with the kind of the step it names. */
|
|
21
|
+
export type PlacedRead = DeclaredRead & { index: number; kind: string };
|
|
22
|
+
|
|
23
|
+
/** `pointer`'s segments, unescaped (RFC 6901). */
|
|
24
|
+
function segments(pointer: string): string[] {
|
|
25
|
+
if (pointer === "") return [];
|
|
26
|
+
return pointer
|
|
27
|
+
.split("/")
|
|
28
|
+
.slice(1)
|
|
29
|
+
.map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** `value` with `ref` placed at `path`, copying each object on the way; `undefined` when something already sits there. */
|
|
33
|
+
function placed(value: Record<string, unknown>, path: readonly string[], ref: unknown): Record<string, unknown> | undefined {
|
|
34
|
+
if (path.length === 0) return undefined;
|
|
35
|
+
const [head, ...rest] = path as [string, ...string[]];
|
|
36
|
+
const here = value[head];
|
|
37
|
+
if (rest.length === 0) return here === undefined ? { ...value, [head]: ref } : undefined;
|
|
38
|
+
if (here !== undefined && (here === null || typeof here !== "object" || Array.isArray(here))) return undefined;
|
|
39
|
+
const inner = placed((here as Record<string, unknown> | undefined) ?? {}, rest, ref);
|
|
40
|
+
return inner ? { ...value, [head]: inner } : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** `phases` with each step `replace` gives a new value for swapped, in place, nested phases included. */
|
|
44
|
+
function mapSteps(phases: readonly DriverPhase[], replace: (step: DriverStep) => DriverStep): DriverPhase[] {
|
|
45
|
+
const walk = (steps: DriverPhase["steps"]): DriverPhase["steps"] =>
|
|
46
|
+
steps.map((step) => {
|
|
47
|
+
if (typeof (step as DriverPhase).phase === "string" && Array.isArray((step as DriverPhase).steps)) {
|
|
48
|
+
return { ...(step as DriverPhase), steps: walk((step as DriverPhase).steps) };
|
|
49
|
+
}
|
|
50
|
+
if ((step as { kind?: unknown }).kind === "gate") return step;
|
|
51
|
+
return replace(step as DriverStep);
|
|
52
|
+
}) as DriverPhase["steps"];
|
|
53
|
+
return phases.map((phase) => ({ ...phase, steps: walk(phase.steps) }));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Each capability's declared reads among `components` (#4466): every read a
|
|
58
|
+
* capability with `declaredReads` reports between two components of the
|
|
59
|
+
* project, its own reads of itself left out. A capability that throws
|
|
60
|
+
* reports none.
|
|
61
|
+
*/
|
|
62
|
+
export async function declaredReadsOf(
|
|
63
|
+
components: readonly DriverComponent[],
|
|
64
|
+
registry: CapabilityRegistry,
|
|
65
|
+
root: string,
|
|
66
|
+
): Promise<PlacedRead[]> {
|
|
67
|
+
const names = new Set(components.map((c) => c.name));
|
|
68
|
+
const byKind = new Map<string, DeclaredReadMember[]>();
|
|
69
|
+
for (const component of components) {
|
|
70
|
+
const seen = new Map<string, number>();
|
|
71
|
+
for (const step of capabilitySteps(component.deploy)) {
|
|
72
|
+
if (!registry.has(step.kind)) continue;
|
|
73
|
+
const index = seen.get(step.kind) ?? 0;
|
|
74
|
+
seen.set(step.kind, index + 1);
|
|
75
|
+
const { kind, ...rest } = step;
|
|
76
|
+
(byKind.get(kind) ?? byKind.set(kind, []).get(kind)!).push({ component: component.name, index, step: rest });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const out: PlacedRead[] = [];
|
|
80
|
+
for (const [kind, members] of byKind) {
|
|
81
|
+
const capability = registry.resolve(kind);
|
|
82
|
+
if (typeof capability.declaredReads !== "function") continue;
|
|
83
|
+
let reported: DeclaredRead[];
|
|
84
|
+
try {
|
|
85
|
+
reported = await capability.declaredReads({ root }, members);
|
|
86
|
+
} catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const r of reported ?? []) {
|
|
90
|
+
if (r.reader === r.upstream || !names.has(r.reader) || !names.has(r.upstream)) continue;
|
|
91
|
+
out.push({ ...r, index: r.index ?? 0, kind });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return out.sort((a, b) => a.reader.localeCompare(b.reader) || a.index - b.index || a.at.localeCompare(b.at));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* `components` with each declared read written in (#4466): the reader's
|
|
99
|
+
* step gets `{ stackOutput: { stack: upstream, name: output } }` at the
|
|
100
|
+
* read's place, unless something sits there already, and the reader depends
|
|
101
|
+
* on the upstream. Components no read names come back as they were.
|
|
102
|
+
*/
|
|
103
|
+
export function applyDeclaredReads(components: readonly DriverComponent[], reads: readonly PlacedRead[]): DriverComponent[] {
|
|
104
|
+
const byReader = new Map<string, PlacedRead[]>();
|
|
105
|
+
for (const r of reads) (byReader.get(r.reader) ?? byReader.set(r.reader, []).get(r.reader)!).push(r);
|
|
106
|
+
return components.map((component) => {
|
|
107
|
+
const mine = byReader.get(component.name);
|
|
108
|
+
if (!mine) return component;
|
|
109
|
+
const kinds = new Map<string, number>();
|
|
110
|
+
const added = new Set<string>();
|
|
111
|
+
const deploy = mapSteps(component.deploy, (step) => {
|
|
112
|
+
const index = kinds.get(step.kind) ?? 0;
|
|
113
|
+
kinds.set(step.kind, index + 1);
|
|
114
|
+
let next: DriverStep = step;
|
|
115
|
+
for (const r of mine.filter((x) => x.kind === step.kind && x.index === index)) {
|
|
116
|
+
const { kind, ...rest } = next;
|
|
117
|
+
const with_ = placed(rest, segments(r.at), { stackOutput: { stack: r.upstream, name: r.output } });
|
|
118
|
+
if (!with_) continue;
|
|
119
|
+
next = { kind, ...with_ } as DriverStep;
|
|
120
|
+
added.add(r.upstream);
|
|
121
|
+
}
|
|
122
|
+
return next;
|
|
123
|
+
});
|
|
124
|
+
const dependsOn = [...(component.dependsOn ?? [])];
|
|
125
|
+
for (const up of [...added].sort()) if (!dependsOn.includes(up)) dependsOn.push(up);
|
|
126
|
+
return { ...component, dependsOn, deploy };
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** {@link declaredReadsOf} and {@link applyDeclaredReads} in one: `components` with their declared reads written in. */
|
|
131
|
+
export async function withDeclaredReads(components: readonly DriverComponent[], registry: CapabilityRegistry, root: string): Promise<DriverComponent[]> {
|
|
132
|
+
const reads = await declaredReadsOf(components, registry, root);
|
|
133
|
+
return reads.length > 0 ? applyDeclaredReads(components, reads) : [...components];
|
|
134
|
+
}
|
|
@@ -64,6 +64,10 @@ export const DEPLOY_UNIT_RULES: readonly DeployUnitRule[] = [
|
|
|
64
64
|
// A Config Connector manifest applied over REST (#4273). Its unit is the
|
|
65
65
|
// step's `stack`, the member its plan names.
|
|
66
66
|
{ kind: "gcp-apply", field: "stack", lexicon: "gcp" },
|
|
67
|
+
// Another workspace member's outputs (#4455). Its unit is the member, so a
|
|
68
|
+
// change signal naming the member puts the component in the change. No
|
|
69
|
+
// lexicon observes it: the member's own pipeline reports its status.
|
|
70
|
+
{ kind: "member-outputs", field: "member", lexicon: "workspace" },
|
|
67
71
|
];
|
|
68
72
|
|
|
69
73
|
/** One resolved unit a component's deploy composition targets. */
|
package/src/components/driver.ts
CHANGED
|
@@ -54,7 +54,8 @@ import { GateNotRecordableError } from "../lifecycle/no-record";
|
|
|
54
54
|
import { componentPlanDigest } from "./gate-plan";
|
|
55
55
|
import { componentDeployInputs } from "./deploy-inputs";
|
|
56
56
|
import type { PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
57
|
-
import type { CapabilityRegistry, DeployContext } from "./capability";
|
|
57
|
+
import type { CapabilityRegistry, DeployContext, GroupMemberStatus } from "./capability";
|
|
58
|
+
import type { FanOutGroup } from "./fan-out";
|
|
58
59
|
import { isPlanMovedError, PLAN_MOVED_EXIT_CODE, TargetsRefusedError, targetRefusal } from "./capability";
|
|
59
60
|
import { isPromotedArtifact } from "./auto-release";
|
|
60
61
|
import type { ReleaseIdentity } from "../telemetry-attribution";
|
|
@@ -222,6 +223,8 @@ export interface DriverRunResult {
|
|
|
222
223
|
* component that ran in an earlier job.
|
|
223
224
|
*/
|
|
224
225
|
componentOutputs: Record<string, Record<string, unknown>>;
|
|
226
|
+
/** How each member of a group that ran ended, as its capability said (ws-107, #4465), by component. Absent when no group ran. */
|
|
227
|
+
groupOutcomes?: Record<string, GroupMemberStatus>;
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
// ── Graph: order + parallel-safe waves from `dependsOn` ──────────────────────
|
|
@@ -391,6 +394,18 @@ export function unresolvedWiringReferences(
|
|
|
391
394
|
return wiringReferences(input).filter((r) => r.value(componentOutputs) === undefined).map(({ source, ref, publish }) => ({ source, ref, publish }));
|
|
392
395
|
}
|
|
393
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Every cross-component reference in `input`, sorted by `ref` and each once,
|
|
399
|
+
* with whether `componentOutputs` holds a value for it. A pull request's
|
|
400
|
+
* report records each as a read between components, known at plan or not.
|
|
401
|
+
*/
|
|
402
|
+
export function wiringReads(
|
|
403
|
+
input: Record<string, unknown>,
|
|
404
|
+
componentOutputs: Record<string, Record<string, unknown>>,
|
|
405
|
+
): Array<WiringReference & { known: boolean }> {
|
|
406
|
+
return wiringReferences(input).map(({ source, ref, publish, value }) => ({ source, ref, publish, known: value(componentOutputs) !== undefined }));
|
|
407
|
+
}
|
|
408
|
+
|
|
394
409
|
/**
|
|
395
410
|
* Every cross-component reference in `input`, each once, sorted by `ref`,
|
|
396
411
|
* with the value it resolves to against a given outputs map.
|
|
@@ -424,6 +439,42 @@ function wiringReferences(input: Record<string, unknown>): Array<WiringReference
|
|
|
424
439
|
return [...found.values()].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
425
440
|
}
|
|
426
441
|
|
|
442
|
+
/**
|
|
443
|
+
* Every cross-component reference in `input` with where it sits, as a JSON
|
|
444
|
+
* Pointer into `input` (ws-107), and the output it names (`publish.<field>`
|
|
445
|
+
* for an artifact). A reference made twice is listed at each place. Sorted
|
|
446
|
+
* by `ref`, then by `at`.
|
|
447
|
+
*/
|
|
448
|
+
export function wiringReferencesAt(input: Record<string, unknown>): Array<WiringReference & { at: string; output: string }> {
|
|
449
|
+
const found: Array<WiringReference & { at: string; output: string }> = [];
|
|
450
|
+
const pointer = (path: readonly string[]): string => path.map((p) => "/" + p.replace(/~/g, "~0").replace(/\//g, "~1")).join("");
|
|
451
|
+
const walk = (value: unknown, path: string[]): void => {
|
|
452
|
+
if (typeof value === "string") {
|
|
453
|
+
if (PRIOR_STEP_REF.test(value)) return;
|
|
454
|
+
const ref = value.match(COMPONENT_ARTIFACT_REF);
|
|
455
|
+
if (ref) {
|
|
456
|
+
const [, source, field] = ref;
|
|
457
|
+
found.push({ source, ref: `${source}.publish.${field}`, publish: true, at: pointer(path), output: `publish.${field}` });
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (Array.isArray(value)) {
|
|
462
|
+
value.forEach((v, i) => walk(v, [...path, String(i)]));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (value && typeof value === "object") {
|
|
466
|
+
if ("stackOutput" in value) {
|
|
467
|
+
const { stack, name } = (value as { stackOutput: { stack: string; name: string } }).stackOutput;
|
|
468
|
+
found.push({ source: stack, ref: `${stack}.${name}`, publish: name === "publish" || name.startsWith("publish."), at: pointer(path), output: name });
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) walk(v, [...path, k]);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
walk(input, []);
|
|
475
|
+
return found.sort((a, b) => a.ref.localeCompare(b.ref) || a.at.localeCompare(b.at));
|
|
476
|
+
}
|
|
477
|
+
|
|
427
478
|
/**
|
|
428
479
|
* The cross-component references in `input` that resolve to another value
|
|
429
480
|
* against `now` than against `read`, sorted (#3999). A pull request's apply
|
|
@@ -1136,6 +1187,27 @@ export interface InterpretRunOptions {
|
|
|
1136
1187
|
releaseIdentity?: (component: string) => ReleaseIdentity | undefined;
|
|
1137
1188
|
/** The addresses each component narrows to (#4300), by component: its `DeployContext.targets`. */
|
|
1138
1189
|
targets?: Readonly<Record<string, readonly string[]>>;
|
|
1190
|
+
/** Groups applied as one (ws-107, #4465). See {@link InterpretGroups}. Absent: every component runs its own deploy. */
|
|
1191
|
+
groups?: InterpretGroups;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* Groups a run applies as one (ws-107, #4465): a promote under a release and
|
|
1196
|
+
* `run --components` hand them over, already placed by `groupFanOut`. Each
|
|
1197
|
+
* group runs once, through `run`, in the wave its members sit in and in place
|
|
1198
|
+
* of their own deploys. A member that does not end `ok` stops the run after
|
|
1199
|
+
* its wave, as a failed component does.
|
|
1200
|
+
*/
|
|
1201
|
+
export interface InterpretGroups {
|
|
1202
|
+
/** The grouped waves, which replace the ones `dependsOn` gives. Every component of the run is in one; an empty wave is passed over. */
|
|
1203
|
+
waves: string[][];
|
|
1204
|
+
/** Each group's members are components of the run, in one wave. */
|
|
1205
|
+
groups: readonly FanOutGroup[];
|
|
1206
|
+
/** Apply one group with the outputs standing, adding each applied member's own: one result per member, and how each ended. */
|
|
1207
|
+
run: (
|
|
1208
|
+
group: FanOutGroup,
|
|
1209
|
+
componentOutputs: Record<string, Record<string, unknown>>,
|
|
1210
|
+
) => Promise<{ results: DriverComponentResult[]; outcomes: Record<string, GroupMemberStatus> }>;
|
|
1139
1211
|
}
|
|
1140
1212
|
|
|
1141
1213
|
/**
|
|
@@ -1207,8 +1279,20 @@ export async function runInterpretDriver(
|
|
|
1207
1279
|
registry: CapabilityRegistry,
|
|
1208
1280
|
options: InterpretRunOptions,
|
|
1209
1281
|
): Promise<DriverRunResult> {
|
|
1210
|
-
const
|
|
1282
|
+
const graph = resolveComponentGraph(components);
|
|
1211
1283
|
const byName = new Map(components.map((c) => [c.name, c]));
|
|
1284
|
+
// ws-107 (#4465): the grouped waves, and each member's group.
|
|
1285
|
+
const waves = options.groups ? options.groups.waves.map((w) => w.filter((name) => byName.has(name))).filter((w) => w.length > 0) : graph.waves;
|
|
1286
|
+
const order = options.groups ? waves.flat() : graph.order;
|
|
1287
|
+
const placed = new Set(order);
|
|
1288
|
+
const unplaced = components.filter((c) => !placed.has(c.name)).map((c) => c.name);
|
|
1289
|
+
if (unplaced.length > 0) throw new Error(`the grouped waves leave out ${unplaced.join(", ")}, which this run runs`);
|
|
1290
|
+
// A group keeps its members in this run; one left with fewer than two is no group, and its member runs its own deploy.
|
|
1291
|
+
const runGroups = (options.groups?.groups ?? [])
|
|
1292
|
+
.map((g) => ({ ...g, components: g.components.filter((c) => byName.has(c)) }))
|
|
1293
|
+
.filter((g) => g.components.length > 1);
|
|
1294
|
+
const groupOf = new Map(runGroups.flatMap((g) => g.components.map((c) => [c, g] as const)));
|
|
1295
|
+
const groupOutcomes: Record<string, GroupMemberStatus> = {};
|
|
1212
1296
|
const componentOutputs: Record<string, Record<string, unknown>> = { ...(options.componentOutputs ?? {}) };
|
|
1213
1297
|
const emit = joinRunEventSinks(options.onRunEvent, progressSinkOf(options.onProgress));
|
|
1214
1298
|
const gates: GateContext = {
|
|
@@ -1227,8 +1311,19 @@ export async function runInterpretDriver(
|
|
|
1227
1311
|
waveLoop: for (const [waveIndex, wave] of waves.entries()) {
|
|
1228
1312
|
const waveNum = waveIndex + 1;
|
|
1229
1313
|
emit?.({ type: "wave-start", wave: waveNum, components: wave });
|
|
1230
|
-
const waveComponents = wave.map((name) => byName.get(name)!);
|
|
1231
|
-
const
|
|
1314
|
+
const waveComponents = wave.filter((name) => !groupOf.has(name)).map((name) => byName.get(name)!);
|
|
1315
|
+
const groupsHere = [...new Set(wave.map((name) => groupOf.get(name)).filter((g): g is FanOutGroup => g !== undefined))];
|
|
1316
|
+
// Groups and the wave's other components run side by side, as a wave's components do.
|
|
1317
|
+
const groupResults = Promise.all(
|
|
1318
|
+
groupsHere.map(async (group) => {
|
|
1319
|
+
for (const name of group.components) emit?.({ type: "component-start", wave: waveNum, component: name });
|
|
1320
|
+
const ran = await options.groups!.run(group, componentOutputs);
|
|
1321
|
+
Object.assign(groupOutcomes, ran.outcomes);
|
|
1322
|
+
for (const result of ran.results) emit?.({ type: "component-settle", wave: waveNum, component: result.component, status: progressStatus(result.status) });
|
|
1323
|
+
return ran.results;
|
|
1324
|
+
}),
|
|
1325
|
+
);
|
|
1326
|
+
const ownResults = await Promise.all(
|
|
1232
1327
|
waveComponents.map(async (component) => {
|
|
1233
1328
|
emit?.({ type: "component-start", wave: waveNum, component: component.name });
|
|
1234
1329
|
const result = await runComponentDeploy(
|
|
@@ -1244,6 +1339,7 @@ export async function runInterpretDriver(
|
|
|
1244
1339
|
return result;
|
|
1245
1340
|
}),
|
|
1246
1341
|
);
|
|
1342
|
+
const waveResults = [...ownResults, ...(await groupResults).flat()];
|
|
1247
1343
|
results.push(...waveResults);
|
|
1248
1344
|
const stopped = waveResults.find((r) => r.status !== "ok");
|
|
1249
1345
|
emit?.({ type: "wave-settle", wave: waveNum, status: progressStatus(stopped?.status ?? "ok") });
|
|
@@ -1262,6 +1358,7 @@ export async function runInterpretDriver(
|
|
|
1262
1358
|
emit?.({ type: "run-settle", status: progressStatus(status), durationMs: null });
|
|
1263
1359
|
const result: DriverRunResult = {
|
|
1264
1360
|
order, waves, results, ok, status, failedComponent, componentOutputs,
|
|
1361
|
+
...(Object.keys(groupOutcomes).length > 0 ? { groupOutcomes } : {}),
|
|
1265
1362
|
...(gated
|
|
1266
1363
|
? {
|
|
1267
1364
|
gatedComponent: gated.component,
|
|
@@ -46,13 +46,14 @@ import {
|
|
|
46
46
|
type GateContext,
|
|
47
47
|
} from "./driver";
|
|
48
48
|
import type { ReleaseIdentity } from "../telemetry-attribution";
|
|
49
|
-
import { downstreamWithin, remainingFanOut, type FanOutPlan, type FanOutProgress, type FanOutSkip } from "./fan-out";
|
|
49
|
+
import { downstreamWithin, remainingFanOut, type FanOutGroup, type FanOutPlan, type FanOutProgress, type FanOutSkip } from "./fan-out";
|
|
50
50
|
import { evaluateGate, gitGateLedgerPort, type GateCheck, type GateLedgerPort } from "../op/gate";
|
|
51
51
|
import type { RunProgressEvent } from "./run-progress";
|
|
52
52
|
import { emitGateStep, joinRunEventSinks, progressSinkOf, type RunEventSink } from "../run-event-stream";
|
|
53
|
-
import type { CapabilityRegistry } from "./capability";
|
|
53
|
+
import type { CapabilityRegistry, GroupMemberStatus, GroupRun } from "./capability";
|
|
54
54
|
import type { PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
55
55
|
import type { DeployContext } from "./capability";
|
|
56
|
+
import { groupsByComponent, narrowGroups, planAndRunComponentGroup, runComponentGroup } from "./groups";
|
|
56
57
|
import { planWaveComponent, type WaveComponentPlan } from "./wave-plan";
|
|
57
58
|
import { computePlanDigest } from "../lifecycle/plan-digest";
|
|
58
59
|
import { decideWaveGate, runWaveLoop, waveGateName, waveSetDigest, type WaveMember, type WaveRecord } from "../wave-engine";
|
|
@@ -247,6 +248,20 @@ export interface FanOutRunOptions {
|
|
|
247
248
|
* before it applied.
|
|
248
249
|
*/
|
|
249
250
|
hold?: (component: string, componentOutputs: Record<string, Record<string, unknown>>) => string | undefined;
|
|
251
|
+
/**
|
|
252
|
+
* The approved plan of each group in `plan.groups` (ws-107), by the
|
|
253
|
+
* group's first component. A group runs through `runGroup` in its wave,
|
|
254
|
+
* in place of its members' own runs, and is held and blocked as one: a
|
|
255
|
+
* member held or blocked holds or blocks the rest. Not with `waveGate`.
|
|
256
|
+
*/
|
|
257
|
+
groupRuns?: Record<string, GroupRun>;
|
|
258
|
+
/**
|
|
259
|
+
* A group with no entry in `groupRuns` plans in its wave against the
|
|
260
|
+
* outputs standing, then applies (#4465): `components fan-out` plans
|
|
261
|
+
* nothing before it runs. Without it such a group fails, which is what a
|
|
262
|
+
* pull request's apply wants for a group nobody approved.
|
|
263
|
+
*/
|
|
264
|
+
planGroupsAtWave?: boolean;
|
|
250
265
|
}
|
|
251
266
|
|
|
252
267
|
/** A component {@link FanOutRunOptions.hold} held back, or one downstream of it. */
|
|
@@ -276,6 +291,14 @@ export interface FanOutRunResult {
|
|
|
276
291
|
waves?: WaveRecord[];
|
|
277
292
|
/** What {@link FanOutRunOptions.hold} held back, in plan order. Absent when nothing was. */
|
|
278
293
|
held?: FanOutHeld[];
|
|
294
|
+
/**
|
|
295
|
+
* Members of a group whose apply stopped before their part began (ws-107),
|
|
296
|
+
* sorted. Each has a `fail` result but is not in `failed`; what depends on
|
|
297
|
+
* it outside the group is blocked by it. Absent when none.
|
|
298
|
+
*/
|
|
299
|
+
notStarted?: string[];
|
|
300
|
+
/** How each member of a group that ran ended, as its capability said (#4446), by component. Absent when no group ran. */
|
|
301
|
+
groupOutcomes?: Record<string, GroupMemberStatus>;
|
|
279
302
|
}
|
|
280
303
|
|
|
281
304
|
/**
|
|
@@ -299,7 +322,8 @@ export async function runFanOut(
|
|
|
299
322
|
|
|
300
323
|
// Resume first, so the gate is decided against the work that is actually left
|
|
301
324
|
// and a re-run does not re-approve. The digest is carried, not recomputed.
|
|
302
|
-
|
|
325
|
+
// A group keeps what is left of it (#4465).
|
|
326
|
+
const active = options.progress ? narrowGroups(remainingFanOut(plan, components, options.progress), components, plan.groups) : plan;
|
|
303
327
|
|
|
304
328
|
if (options.gate && options.waveGate) {
|
|
305
329
|
throw new Error("a fan-out takes one gate over the set or one gate per wave, not both");
|
|
@@ -307,6 +331,9 @@ export async function runFanOut(
|
|
|
307
331
|
if (options.waveGate && options.plans) {
|
|
308
332
|
throw new Error("a gated-wave fan-out plans each wave itself, so it takes no plans made beforehand");
|
|
309
333
|
}
|
|
334
|
+
if (options.waveGate && plan.groups?.length) {
|
|
335
|
+
throw new Error("a gated-wave fan-out plans component by component, so it takes no groups");
|
|
336
|
+
}
|
|
310
337
|
if (options.waveGate) {
|
|
311
338
|
return runGatedWaves(plan, active, components, registry, options, options.waveGate, gates, componentOutputs);
|
|
312
339
|
}
|
|
@@ -348,44 +375,80 @@ export async function runFanOut(
|
|
|
348
375
|
const results: DriverComponentResult[] = [];
|
|
349
376
|
const completed: string[] = [];
|
|
350
377
|
const failed: string[] = [];
|
|
378
|
+
const notStarted: string[] = [];
|
|
379
|
+
const groupOutcomes: Record<string, GroupMemberStatus> = {};
|
|
351
380
|
const blockedBy = new Map<string, string>();
|
|
352
381
|
const held = new Map<string, FanOutHeld>();
|
|
353
382
|
let gatedFact: PendingGateRecord | undefined;
|
|
354
383
|
|
|
355
384
|
const inPlan = new Set(active.order);
|
|
356
385
|
const waveOf = waveNumbers(active.waves);
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
386
|
+
// ws-107: a group applies as one unit, so it is held and blocked as one.
|
|
387
|
+
const groupOf = groupsByComponent(active);
|
|
388
|
+
/** Hold `root` and everything downstream of it that has not settled otherwise. With `heldBy`, `root` is held for that component's reason. */
|
|
389
|
+
const markHeld = (root: string, reason: string, heldBy?: string): void => {
|
|
390
|
+
held.set(root, { component: root, reason, ...(heldBy ? { heldBy } : {}) });
|
|
360
391
|
emit?.({ type: "component-settle", wave: waveOf.get(root)!, component: root, status: "waiting" });
|
|
361
392
|
for (const [component] of downstreamWithin(components, inPlan, [root])) {
|
|
362
393
|
if (held.has(component) || blockedBy.has(component) || failed.includes(component)) continue;
|
|
363
|
-
held.set(component, { component, reason, heldBy: root });
|
|
394
|
+
held.set(component, { component, reason, heldBy: heldBy ?? root });
|
|
364
395
|
emit?.({ type: "component-settle", wave: waveOf.get(component)!, component, status: "waiting" });
|
|
365
396
|
}
|
|
366
397
|
};
|
|
367
|
-
/** Skip everything downstream of `
|
|
368
|
-
const markBlocked = (
|
|
369
|
-
for (const [component, by] of downstreamWithin(components, inPlan,
|
|
370
|
-
if (blockedBy.has(component) || failed.includes(component)) continue;
|
|
398
|
+
/** Skip everything downstream of `roots`, blaming a root rather than the nearest edge. */
|
|
399
|
+
const markBlocked = (...roots: string[]): void => {
|
|
400
|
+
for (const [component, by] of downstreamWithin(components, inPlan, roots)) {
|
|
401
|
+
if (blockedBy.has(component) || failed.includes(component) || completed.includes(component) || notStarted.includes(component)) continue;
|
|
371
402
|
blockedBy.set(component, by);
|
|
372
403
|
}
|
|
373
404
|
};
|
|
374
405
|
|
|
375
406
|
for (const [waveIndex, wave] of active.waves.entries()) {
|
|
376
407
|
const waveNum = waveIndex + 1;
|
|
408
|
+
// A member blocked blocks its whole group, by the same failure.
|
|
409
|
+
for (const name of wave) {
|
|
410
|
+
const by = blockedBy.get(name);
|
|
411
|
+
if (by === undefined) continue;
|
|
412
|
+
for (const mate of groupOf.get(name)?.components ?? []) if (!blockedBy.has(mate) && mate !== by) blockedBy.set(mate, by);
|
|
413
|
+
}
|
|
377
414
|
// A component whose upstream failed in an earlier wave is not dispatched.
|
|
378
415
|
const candidates = wave.filter((name) => inPlan.has(name) && !blockedBy.has(name) && !held.has(name));
|
|
379
416
|
for (const name of candidates) {
|
|
417
|
+
if (held.has(name)) continue;
|
|
380
418
|
const reason = options.hold?.(name, componentOutputs);
|
|
381
|
-
if (reason
|
|
419
|
+
if (reason === undefined) continue;
|
|
420
|
+
markHeld(name, reason);
|
|
421
|
+
for (const mate of groupOf.get(name)?.components ?? []) if (!held.has(mate)) markHeld(mate, reason, name);
|
|
382
422
|
}
|
|
383
423
|
const runnable = candidates.filter((name) => !held.has(name));
|
|
384
424
|
if (runnable.length === 0) continue;
|
|
385
425
|
|
|
386
426
|
emit?.({ type: "wave-start", wave: waveNum, components: runnable });
|
|
427
|
+
const groupsHere = [...new Set(runnable.map((name) => groupOf.get(name)).filter((g): g is FanOutGroup => g !== undefined))];
|
|
428
|
+
// Groups and the wave's other components run side by side, as a wave's components do.
|
|
429
|
+
const groupRuns = Promise.all(
|
|
430
|
+
groupsHere.map(async (group) => {
|
|
431
|
+
for (const name of group.components) emit?.({ type: "component-start", wave: waveNum, component: name });
|
|
432
|
+
const approved = options.groupRuns?.[group.components[0]!];
|
|
433
|
+
const each = {
|
|
434
|
+
group,
|
|
435
|
+
components: byName,
|
|
436
|
+
registry,
|
|
437
|
+
env: options.env,
|
|
438
|
+
...(options.vars ? { vars: options.vars } : {}),
|
|
439
|
+
ctxOf: (name: string) => fanOutContext(options, name),
|
|
440
|
+
componentOutputs,
|
|
441
|
+
};
|
|
442
|
+
const ran = approved === undefined && options.planGroupsAtWave ? await planAndRunComponentGroup(each) : await runComponentGroup({ ...each, run: approved });
|
|
443
|
+
for (const result of ran.results) {
|
|
444
|
+
emit?.({ type: "component-settle", wave: waveNum, component: result.component, status: result.status === "fail" ? "failed" : result.status });
|
|
445
|
+
options.onComponentSettled?.(result, componentOutputs[result.component]);
|
|
446
|
+
}
|
|
447
|
+
return ran;
|
|
448
|
+
}),
|
|
449
|
+
);
|
|
387
450
|
const waveResults = await Promise.all(
|
|
388
|
-
runnable.map(async (name) => {
|
|
451
|
+
runnable.filter((name) => !groupOf.has(name)).map(async (name) => {
|
|
389
452
|
emit?.({ type: "component-start", wave: waveNum, component: name });
|
|
390
453
|
const planned = options.plans?.[name];
|
|
391
454
|
const result = await runComponentDeploy(
|
|
@@ -407,6 +470,7 @@ export async function runFanOut(
|
|
|
407
470
|
return result;
|
|
408
471
|
}),
|
|
409
472
|
);
|
|
473
|
+
const groupResults = await groupRuns;
|
|
410
474
|
results.push(...waveResults);
|
|
411
475
|
|
|
412
476
|
for (const result of waveResults) {
|
|
@@ -425,8 +489,22 @@ export async function runFanOut(
|
|
|
425
489
|
gatedFact ??= result.gate;
|
|
426
490
|
markBlocked(result.component);
|
|
427
491
|
}
|
|
492
|
+
// A group's members settle together; what depends on a member that failed
|
|
493
|
+
// or never started, outside the group, is blocked by that member.
|
|
494
|
+
for (const ran of groupResults) {
|
|
495
|
+
results.push(...ran.results);
|
|
496
|
+
Object.assign(groupOutcomes, ran.outcomes);
|
|
497
|
+
const stopped: string[] = [];
|
|
498
|
+
for (const result of ran.results) {
|
|
499
|
+
if (result.status === "ok") completed.push(result.component);
|
|
500
|
+
else if (ran.notStarted.includes(result.component)) notStarted.push(result.component);
|
|
501
|
+
else failed.push(result.component);
|
|
502
|
+
if (result.status !== "ok") stopped.push(result.component);
|
|
503
|
+
}
|
|
504
|
+
if (stopped.length > 0) markBlocked(...stopped);
|
|
505
|
+
}
|
|
428
506
|
|
|
429
|
-
const waveFailed = waveResults.some((r) => r.status === "fail");
|
|
507
|
+
const waveFailed = waveResults.some((r) => r.status === "fail") || groupResults.some((ran) => ran.results.some((r) => r.status === "fail"));
|
|
430
508
|
emit?.({
|
|
431
509
|
type: "wave-settle",
|
|
432
510
|
wave: waveNum,
|
|
@@ -442,7 +520,7 @@ export async function runFanOut(
|
|
|
442
520
|
|
|
443
521
|
// A failure outranks a gate: something is actually broken, and reporting the
|
|
444
522
|
// run as "waiting on a human" would hide it. Same precedence the driver uses.
|
|
445
|
-
const status: FanOutRunResult["status"] = failed.length > 0 ? "fail" : gatedFact ? "gated" : "ok";
|
|
523
|
+
const status: FanOutRunResult["status"] = failed.length > 0 || notStarted.length > 0 ? "fail" : gatedFact ? "gated" : "ok";
|
|
446
524
|
emit?.({ type: "run-settle", status: status === "fail" ? "failed" : status, durationMs: null });
|
|
447
525
|
|
|
448
526
|
return {
|
|
@@ -455,6 +533,8 @@ export async function runFanOut(
|
|
|
455
533
|
componentOutputs,
|
|
456
534
|
...(gatedFact ? { gate: gatedFact } : {}),
|
|
457
535
|
...(held.size > 0 ? { held: active.order.filter((name) => held.has(name)).map((name) => held.get(name)!) } : {}),
|
|
536
|
+
...(notStarted.length > 0 ? { notStarted: [...notStarted].sort() } : {}),
|
|
537
|
+
...(Object.keys(groupOutcomes).length > 0 ? { groupOutcomes } : {}),
|
|
458
538
|
};
|
|
459
539
|
}
|
|
460
540
|
|
|
@@ -19,6 +19,7 @@ import { applyConfigDefaults } from "./config-defaults";
|
|
|
19
19
|
import { buildCapabilityRegistry } from "./capability-plugin-loader";
|
|
20
20
|
import { componentsForUnits, planFanOut, type ChangedUnits, type ComponentChangeSignal, type FanOutPlan } from "./fan-out";
|
|
21
21
|
import type { CapabilityRegistry } from "./capability";
|
|
22
|
+
import { withDeclaredReads } from "./declared-reads";
|
|
22
23
|
import type { DriverComponent } from "./driver";
|
|
23
24
|
import type { ComponentEnvironmentDeclaration } from "./component";
|
|
24
25
|
import type { BuildParamProvenance } from "../provenance";
|
|
@@ -36,6 +37,12 @@ export interface DeriveFanOutOptions {
|
|
|
36
37
|
config?: ChantConfig;
|
|
37
38
|
/** Components that form wave 1 (#3049). See `FanOutRequest.canary`. */
|
|
38
39
|
canary?: string[];
|
|
40
|
+
/**
|
|
41
|
+
* The registry whose capabilities' `declaredReads` (#4466) are written into
|
|
42
|
+
* the components before the order is derived. Absent, the project's own
|
|
43
|
+
* (`fanOutRegistry`) is built for it.
|
|
44
|
+
*/
|
|
45
|
+
registry?: CapabilityRegistry;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export interface DerivedFanOut {
|
|
@@ -78,7 +85,9 @@ export async function deriveFanOut(options: DeriveFanOutOptions): Promise<Derive
|
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
const { config } = options.config ? { config: options.config } : await loadChantConfig(options.path);
|
|
81
|
-
const
|
|
88
|
+
const defaulted = resolved.targets.map((component) => applyConfigDefaults(component, config));
|
|
89
|
+
// #4466: the reads a capability finds in its steps' files, such as a template's Fn::ImportValue, order and hold like declared ones.
|
|
90
|
+
const components = await withDeclaredReads(defaulted, options.registry ?? (await fanOutRegistry(options.path, config)), options.path);
|
|
82
91
|
|
|
83
92
|
const signal = componentsForUnits(components, options.units);
|
|
84
93
|
const plan = planFanOut({
|