@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
|
@@ -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({
|
|
@@ -37,6 +37,7 @@ import { resolveComponentGraph, type DriverComponent } from "./driver";
|
|
|
37
37
|
import { deployUnits } from "./deploy-units";
|
|
38
38
|
import { computePlanDigest } from "../lifecycle/plan-digest";
|
|
39
39
|
import { layerWaves } from "../gated-waves";
|
|
40
|
+
import type { ReportedGroupRead } from "./capability";
|
|
40
41
|
|
|
41
42
|
/** Thrown when `changed` or `indeterminate` names a component the project does not have. */
|
|
42
43
|
export class UnknownComponentError extends Error {
|
|
@@ -124,6 +125,35 @@ export interface FanOutPlan {
|
|
|
124
125
|
indeterminate: string[];
|
|
125
126
|
/** Identity of this derivation, for binding one approval to the whole fan-out (#2300's pattern). */
|
|
126
127
|
digest: string;
|
|
128
|
+
/**
|
|
129
|
+
* The groups a pull request plans and applies as one (ws-107), each in
|
|
130
|
+
* one wave, set by `groupFanOut` (./groups.ts). `waves` are then the
|
|
131
|
+
* grouped waves. `digest` stays the derivation's. Absent when none formed.
|
|
132
|
+
*/
|
|
133
|
+
groups?: FanOutGroup[];
|
|
134
|
+
/** The components that could have grouped and did not, each with why (ws-107, ruling 1). Absent when none. */
|
|
135
|
+
fallbacks?: FanOutGroupFallback[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Components one capability plans and applies in one call (ws-107). */
|
|
139
|
+
export interface FanOutGroup {
|
|
140
|
+
/** The kind of the capability every member's one step names. */
|
|
141
|
+
capability: string;
|
|
142
|
+
/** The members, upstreams first. */
|
|
143
|
+
components: string[];
|
|
144
|
+
/**
|
|
145
|
+
* The reads between members the capability reported through `groupReads`
|
|
146
|
+
* (#4447), which no member's step input shows. Absent when none.
|
|
147
|
+
*/
|
|
148
|
+
reads?: ReportedGroupRead[];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** A component that did not join a group (ws-107): its capability's `kind`, and the fallback's `kind` and `reason`. */
|
|
152
|
+
export interface FanOutGroupFallback {
|
|
153
|
+
component: string;
|
|
154
|
+
capability: string;
|
|
155
|
+
kind: string;
|
|
156
|
+
reason: string;
|
|
127
157
|
}
|
|
128
158
|
|
|
129
159
|
/** Reverse the `dependsOn` edges: who has to re-run when this one moves. */
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which components form a group and where it sits (ws-107, #4440), against
|
|
3
|
+
* fake capabilities: `fake-group` declares `planGroup` and `runGroup`,
|
|
4
|
+
* `fake-plain` does not. Planning and applying a group in a pull request is
|
|
5
|
+
* in `pr-run.test.ts`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { describe, expect, test } from "vitest";
|
|
11
|
+
import { CapabilityRegistry, type GroupFallback, type GroupMember, type ReportedGroupRead } from "./capability";
|
|
12
|
+
import { planFanOut } from "./fan-out";
|
|
13
|
+
import { findComponentGroups, groupFanOut, narrowGroups, planComponentGroup } from "./groups";
|
|
14
|
+
import { prWavePlan } from "./pr-run";
|
|
15
|
+
import type { DriverComponent } from "./driver";
|
|
16
|
+
|
|
17
|
+
const so = (stack: string, name: string) => ({ stackOutput: { stack, name } });
|
|
18
|
+
|
|
19
|
+
const component = (kind: string, name: string, dependsOn: string[] = [], vars?: Record<string, unknown>, extra: Array<Record<string, unknown>> = []): DriverComponent => ({
|
|
20
|
+
name,
|
|
21
|
+
dependsOn,
|
|
22
|
+
deploy: [{ phase: "Apply", steps: [{ kind, root: name, ...(vars ? { vars } : {}) }, ...(extra as never[])] }],
|
|
23
|
+
});
|
|
24
|
+
const grouped = (name: string, dependsOn: string[] = [], vars?: Record<string, unknown>) => component("fake-group", name, dependsOn, vars);
|
|
25
|
+
const plain = (name: string, dependsOn: string[] = [], vars?: Record<string, unknown>) => component("fake-plain", name, dependsOn, vars);
|
|
26
|
+
|
|
27
|
+
function registry(fallback?: (members: readonly GroupMember[]) => GroupFallback | undefined) {
|
|
28
|
+
const asked: GroupMember[][] = [];
|
|
29
|
+
const r = new CapabilityRegistry();
|
|
30
|
+
const unused = async (): Promise<never> => {
|
|
31
|
+
throw new Error("not called here");
|
|
32
|
+
};
|
|
33
|
+
r.register({
|
|
34
|
+
kind: "fake-group",
|
|
35
|
+
run: unused,
|
|
36
|
+
plan: unused,
|
|
37
|
+
planGroup: unused,
|
|
38
|
+
runGroup: unused,
|
|
39
|
+
...(fallback
|
|
40
|
+
? {
|
|
41
|
+
async groupFallback(_ctx: unknown, members: readonly GroupMember[]) {
|
|
42
|
+
asked.push([...members]);
|
|
43
|
+
return fallback(members);
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
: {}),
|
|
47
|
+
} as never);
|
|
48
|
+
r.register({ kind: "fake-plain", run: unused, plan: unused } as never);
|
|
49
|
+
return { registry: r, asked };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function groupsOf(components: DriverComponent[], changed: string[], r = registry().registry) {
|
|
53
|
+
const plan = planFanOut({ components, changed });
|
|
54
|
+
const found = await findComponentGroups({ components, plan, registry: r, env: "prod" });
|
|
55
|
+
return { plan, found, grouped: groupFanOut(plan, components, found) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("which components form a group (ws-107)", () => {
|
|
59
|
+
/** a; b reads a; c reads b. */
|
|
60
|
+
const CHAIN = [grouped("a"), grouped("b", ["a"], { upstream: so("a", "id") }), grouped("c", ["b"], { upstream: so("b", "arn") })];
|
|
61
|
+
|
|
62
|
+
test("one-step components of a group capability joined by reads form one group, upstreams first", async () => {
|
|
63
|
+
const { found } = await groupsOf(CHAIN, ["a"]);
|
|
64
|
+
expect(found).toEqual({ groups: [{ capability: "fake-group", components: ["a", "b", "c"] }], fallbacks: [] });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("a component that reads none of the others, or is of another capability, joins nothing and is not listed", async () => {
|
|
68
|
+
const components = [...CHAIN, grouped("lone", ["a"]), plain("p", ["a"], { upstream: so("a", "id") })];
|
|
69
|
+
const { found } = await groupsOf(components, ["a"]);
|
|
70
|
+
expect(found.groups).toEqual([{ capability: "fake-group", components: ["a", "b", "c"] }]);
|
|
71
|
+
expect(found.fallbacks).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("a component outside the set on a dependency path between two members splits it (ruling 2)", async () => {
|
|
75
|
+
// c reads a too, but m sits between them.
|
|
76
|
+
const components = [grouped("a"), plain("m", ["a"], { upstream: so("a", "id") }), grouped("c", ["m"], { upstream: so("m", "x"), first: so("a", "id") })];
|
|
77
|
+
const { found } = await groupsOf(components, ["a"]);
|
|
78
|
+
expect(found.groups).toEqual([]);
|
|
79
|
+
expect(found.fallbacks.map((f) => [f.component, f.capability, f.kind])).toEqual([
|
|
80
|
+
["a", "fake-group", "split"],
|
|
81
|
+
["c", "fake-group", "split"],
|
|
82
|
+
]);
|
|
83
|
+
expect(found.fallbacks[0]!.reason).toMatch(/^m lies on a dependency path between a/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("a component whose group step is one of several does not join (ruling 3), and is listed as steps", async () => {
|
|
87
|
+
const components = [grouped("a"), component("fake-group", "s", ["a"], { upstream: so("a", "id") }, [{ kind: "gate", gate: "ok" }])];
|
|
88
|
+
const { found } = await groupsOf(components, ["a"]);
|
|
89
|
+
expect(found.groups).toEqual([]);
|
|
90
|
+
expect(found.fallbacks).toEqual([
|
|
91
|
+
{ component: "s", capability: "fake-group", kind: "steps", reason: "s deploys in 2 steps, and only a component whose deploy is one fake-group step joins a group" },
|
|
92
|
+
]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("the capability's groupFallback is asked before anything plans, with each member's reads and no input, and dissolves the group", async () => {
|
|
96
|
+
const r = registry(() => ({ kind: "off", reason: "grouping is turned off" }));
|
|
97
|
+
const { found, grouped: plan, plan: derived } = await groupsOf(CHAIN, ["a"], r.registry);
|
|
98
|
+
expect(r.asked).toHaveLength(1);
|
|
99
|
+
expect(r.asked[0]!.map((m) => m.component)).toEqual(["a", "b", "c"]);
|
|
100
|
+
expect(r.asked[0]![1]).toMatchObject({ component: "b", step: { root: "b", vars: { upstream: so("a", "id") } }, reads: [{ at: "/vars/upstream", ref: "a.id", component: "a", output: "id" }] });
|
|
101
|
+
expect(r.asked[0]![1]!.input).toBeUndefined();
|
|
102
|
+
expect(found.groups).toEqual([]);
|
|
103
|
+
expect(found.fallbacks.map((f) => [f.component, f.kind, f.reason])).toEqual([
|
|
104
|
+
["a", "off", "grouping is turned off"],
|
|
105
|
+
["b", "off", "grouping is turned off"],
|
|
106
|
+
["c", "off", "grouping is turned off"],
|
|
107
|
+
]);
|
|
108
|
+
// No group: the waves are the derivation's.
|
|
109
|
+
expect(plan.waves).toEqual(derived.waves);
|
|
110
|
+
expect(plan.groups).toBeUndefined();
|
|
111
|
+
expect(plan.fallbacks).toEqual(found.fallbacks);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("a groupFallback that drops members (#4419 ruling 1)", () => {
|
|
116
|
+
/** A fallback that drops `names` while any of them is still in the group, and takes the group once none is. */
|
|
117
|
+
const dropping = (names: string[]) => (members: readonly GroupMember[]): GroupFallback | undefined => {
|
|
118
|
+
const hit = members.filter((m) => names.includes(m.component)).map((m) => m.component);
|
|
119
|
+
return hit.length === 0 ? undefined : { kind: "lock", reason: "holds no lock", drop: hit.map((component) => ({ component, reason: `${component} holds no lock` })) };
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
test("dropping one of three leaves the other two grouped; core asks again with the two, and the report names the one", async () => {
|
|
123
|
+
const components = [grouped("a"), grouped("b", ["a"], { upstream: so("a", "id") }), grouped("c", ["b"], { upstream: so("b", "id") })];
|
|
124
|
+
const r = registry(dropping(["c"]));
|
|
125
|
+
const { found, grouped: plan } = await groupsOf(components, ["a"], r.registry);
|
|
126
|
+
expect(r.asked.map((ms) => ms.map((m) => m.component))).toEqual([
|
|
127
|
+
["a", "b", "c"],
|
|
128
|
+
["a", "b"],
|
|
129
|
+
]);
|
|
130
|
+
expect(found.groups).toEqual([{ capability: "fake-group", components: ["a", "b"] }]);
|
|
131
|
+
expect(found.fallbacks).toEqual([{ component: "c", capability: "fake-group", kind: "lock", reason: "c holds no lock" }]);
|
|
132
|
+
expect(plan.fallbacks).toEqual(found.fallbacks);
|
|
133
|
+
// c keeps its wave after the group; b's emptied wave stays so wave numbers keep their meaning (ws-107).
|
|
134
|
+
expect(plan.waves).toEqual([["a", "b"], [], ["c"]]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("a dropped member on a dependency path between the rest splits them, each as split", async () => {
|
|
138
|
+
// c reads a and b; b reads a. Without b, a and c still read each other, but b applies between them.
|
|
139
|
+
const components = [grouped("a"), grouped("b", ["a"], { upstream: so("a", "id") }), grouped("c", ["a", "b"], { first: so("a", "id"), second: so("b", "id") })];
|
|
140
|
+
const r = registry(dropping(["b"]));
|
|
141
|
+
const { found } = await groupsOf(components, ["a"], r.registry);
|
|
142
|
+
expect(r.asked).toHaveLength(1);
|
|
143
|
+
expect(found.groups).toEqual([]);
|
|
144
|
+
expect(found.fallbacks.map((f) => [f.component, f.kind])).toEqual([
|
|
145
|
+
["a", "split"],
|
|
146
|
+
["b", "lock"],
|
|
147
|
+
["c", "split"],
|
|
148
|
+
]);
|
|
149
|
+
expect(found.fallbacks[0]!.reason).toMatch(/^b lies on a dependency path between a/);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("a drop that leaves one member is no group, and that member falls back as alone", async () => {
|
|
153
|
+
const components = [grouped("a"), grouped("b", ["a"], { upstream: so("a", "id") })];
|
|
154
|
+
const r = registry(dropping(["a"]));
|
|
155
|
+
const { found, grouped: plan, plan: derived } = await groupsOf(components, ["a"], r.registry);
|
|
156
|
+
expect(r.asked).toHaveLength(1);
|
|
157
|
+
expect(found.groups).toEqual([]);
|
|
158
|
+
expect(found.fallbacks).toEqual([
|
|
159
|
+
{ component: "a", capability: "fake-group", kind: "lock", reason: "a holds no lock" },
|
|
160
|
+
{ component: "b", capability: "fake-group", kind: "alone", reason: "b is the only member of its group left once a fell back, and a group of one is no group" },
|
|
161
|
+
]);
|
|
162
|
+
expect(plan.waves).toEqual(derived.waves);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("a drop that names no member of the group dissolves it with the fallback's kind", async () => {
|
|
166
|
+
const r = registry(() => ({ kind: "lock", reason: "holds no lock", drop: [{ component: "elsewhere" }] }));
|
|
167
|
+
const components = [grouped("a"), grouped("b", ["a"], { upstream: so("a", "id") })];
|
|
168
|
+
const { found } = await groupsOf(components, ["a"], r.registry);
|
|
169
|
+
expect(found.groups).toEqual([]);
|
|
170
|
+
expect(found.fallbacks.map((f) => [f.component, f.kind, f.reason])).toEqual([
|
|
171
|
+
["a", "lock", "holds no lock"],
|
|
172
|
+
["b", "lock", "holds no lock"],
|
|
173
|
+
]);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("where a group sits (ws-107)", () => {
|
|
178
|
+
test("in the earliest wave any member is in; the waves it left stay, empty, and the digest is the derivation's", async () => {
|
|
179
|
+
const components = [grouped("a"), grouped("b", ["a"], { upstream: so("a", "id") }), grouped("c", ["b"], { upstream: so("b", "id") })];
|
|
180
|
+
const { plan, grouped: g } = await groupsOf(components, ["a"]);
|
|
181
|
+
expect(plan.waves).toEqual([["a"], ["b"], ["c"]]);
|
|
182
|
+
expect(g.waves).toEqual([["a", "b", "c"], [], []]);
|
|
183
|
+
expect(g.order).toEqual(["a", "b", "c"]);
|
|
184
|
+
expect(g.digest).toBe(plan.digest);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("after everything its members depend on outside it; what depends on a member moves after the group", async () => {
|
|
188
|
+
// b reads a and depends on u too; z depends on a.
|
|
189
|
+
const components = [plain("u"), grouped("a"), grouped("b", ["a", "u"], { upstream: so("a", "id") }), plain("z", ["a"])];
|
|
190
|
+
const { plan, grouped: g } = await groupsOf(components, ["a", "u"]);
|
|
191
|
+
expect(plan.waves).toEqual([["a", "u"], ["b", "z"]]);
|
|
192
|
+
expect(g.groups).toEqual([{ capability: "fake-group", components: ["a", "b"] }]);
|
|
193
|
+
expect(g.waves).toEqual([["u"], ["a", "b"], ["z"]]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("a round's narrowing keeps what is left of a group as one; a wave gate's wave carries the group (ws-107)", async () => {
|
|
197
|
+
const components = [plain("x"), grouped("a", ["x"], { upstream: so("x", "id") }), grouped("b", ["a"], { upstream: so("a", "id") })];
|
|
198
|
+
const { grouped: g } = await groupsOf(components, ["x"]);
|
|
199
|
+
expect(g.waves).toEqual([["x"], ["a", "b"], []]);
|
|
200
|
+
const left = narrowGroups({ ...g, order: ["a", "b"], waves: [["a"], ["b"]], groups: undefined }, components, g.groups);
|
|
201
|
+
expect(left.groups).toEqual(g.groups);
|
|
202
|
+
expect(left.waves).toEqual([["a", "b"], []]);
|
|
203
|
+
const wave = prWavePlan(g, components, 2, ["x"]);
|
|
204
|
+
expect(wave).toMatchObject({ order: ["a", "b"], waves: [["a", "b"]], groups: g.groups, seeds: ["x"], digest: g.digest });
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("reads a capability reports through groupReads (#4447)", () => {
|
|
209
|
+
/** A registry whose `fake-group` reports `reads` and records what it was asked. */
|
|
210
|
+
function reporting(reads: ReportedGroupRead[]) {
|
|
211
|
+
const asked: Array<{ components: string[]; members: string[] }> = [];
|
|
212
|
+
const r = new CapabilityRegistry();
|
|
213
|
+
const unused = async (): Promise<never> => {
|
|
214
|
+
throw new Error("not called here");
|
|
215
|
+
};
|
|
216
|
+
r.register({
|
|
217
|
+
kind: "fake-group",
|
|
218
|
+
run: unused,
|
|
219
|
+
plan: unused,
|
|
220
|
+
runGroup: unused,
|
|
221
|
+
async planGroup(_ctx: unknown, members: readonly GroupMember[]) {
|
|
222
|
+
// a knows nothing at plan; b's outputs are all known.
|
|
223
|
+
const plans = Object.fromEntries(
|
|
224
|
+
members.map((m) => [m.component, { member: m.component, planDigest: `d-${m.component}`, outputs: m.component === "a" ? { known: {}, unknown: ["id"] } : { known: { arn: "x" }, unknown: [] } }]),
|
|
225
|
+
);
|
|
226
|
+
return { members: plans, digest: "g" };
|
|
227
|
+
},
|
|
228
|
+
async groupReads(ctx: { components: string[] }, members: readonly GroupMember[]) {
|
|
229
|
+
asked.push({ components: [...ctx.components], members: members.map((m) => m.component) });
|
|
230
|
+
return reads;
|
|
231
|
+
},
|
|
232
|
+
} as never);
|
|
233
|
+
r.register({ kind: "fake-plain", run: unused, plan: unused } as never);
|
|
234
|
+
return { registry: r, asked };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
test("a reported read joins two components with no stackOutput read, upstream first, and is kept as the group's reads", async () => {
|
|
238
|
+
// a reads b only in a file core cannot see, and declares no dependsOn.
|
|
239
|
+
const components = [grouped("a"), grouped("b")];
|
|
240
|
+
const r = reporting([
|
|
241
|
+
{ reader: "a", upstream: "b", ref: "data.remote.b" },
|
|
242
|
+
{ reader: "a", upstream: "a", ref: "self" },
|
|
243
|
+
{ reader: "a", upstream: "elsewhere", ref: "not.a.component" },
|
|
244
|
+
]);
|
|
245
|
+
const { found } = await groupsOf(components, ["a", "b"], r.registry);
|
|
246
|
+
expect(r.asked).toHaveLength(1);
|
|
247
|
+
expect([...r.asked[0]!.members].sort()).toEqual(["a", "b"]);
|
|
248
|
+
expect(found).toEqual({
|
|
249
|
+
groups: [{ capability: "fake-group", components: ["b", "a"], reads: [{ reader: "a", upstream: "b", ref: "data.remote.b" }] }],
|
|
250
|
+
fallbacks: [],
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("a reported read the upstream's plan does not know is one of the reader's group reads in its change-set part", async () => {
|
|
255
|
+
const components = [grouped("a"), grouped("b")];
|
|
256
|
+
const r = reporting([{ reader: "b", upstream: "a", ref: "data.remote.a" }]);
|
|
257
|
+
const planned = await planComponentGroup({
|
|
258
|
+
group: { capability: "fake-group", components: ["a", "b"], reads: [{ reader: "b", upstream: "a", ref: "data.remote.a" }] },
|
|
259
|
+
components: new Map(components.map((c) => [c.name, c])),
|
|
260
|
+
registry: r.registry,
|
|
261
|
+
env: "prod",
|
|
262
|
+
ctxOf: (component) => ({ env: "prod", component }),
|
|
263
|
+
outputs: {},
|
|
264
|
+
});
|
|
265
|
+
const reads = Object.fromEntries(planned.plans.map((p) => [p.component, "error" in p ? p.error : p.parts?.[0]?.member.group?.reads]));
|
|
266
|
+
expect(reads).toEqual({ a: [], b: ["data.remote.a"] });
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("a reported read on a dependency path through a component of another kind splits the group (ruling 2)", async () => {
|
|
270
|
+
// b reads a in a file; m, of another kind, depends on a and b depends on m.
|
|
271
|
+
const components = [grouped("a"), plain("m", ["a"]), grouped("b", ["m"])];
|
|
272
|
+
const r = reporting([{ reader: "b", upstream: "a", ref: "data.remote.a" }]);
|
|
273
|
+
const { found } = await groupsOf(components, ["a"], r.registry);
|
|
274
|
+
expect(found.groups).toEqual([]);
|
|
275
|
+
expect(found.fallbacks.map((f) => [f.component, f.kind])).toEqual([
|
|
276
|
+
["a", "split"],
|
|
277
|
+
["b", "split"],
|
|
278
|
+
]);
|
|
279
|
+
expect(found.fallbacks[0]!.reason).toMatch(/^m lies on a dependency path between a/);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("a capability whose groupReads throws reports no reads", async () => {
|
|
283
|
+
const r = new CapabilityRegistry();
|
|
284
|
+
const unused = async (): Promise<never> => {
|
|
285
|
+
throw new Error("not called here");
|
|
286
|
+
};
|
|
287
|
+
r.register({ kind: "fake-group", run: unused, plan: unused, planGroup: unused, runGroup: unused, groupReads: unused } as never);
|
|
288
|
+
const { found } = await groupsOf([grouped("a"), grouped("b")], ["a", "b"], r);
|
|
289
|
+
expect(found).toEqual({ groups: [], fallbacks: [] });
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("narrowing a group keeps only the reported reads between members left", async () => {
|
|
293
|
+
const components = [grouped("a"), grouped("b"), grouped("c")];
|
|
294
|
+
const r = reporting([
|
|
295
|
+
{ reader: "b", upstream: "a", ref: "data.remote.a" },
|
|
296
|
+
{ reader: "c", upstream: "b", ref: "data.remote.b" },
|
|
297
|
+
]);
|
|
298
|
+
const { grouped: g } = await groupsOf(components, ["a", "b", "c"], r.registry);
|
|
299
|
+
expect(g.groups).toEqual([{ capability: "fake-group", components: ["a", "b", "c"], reads: [
|
|
300
|
+
{ reader: "b", upstream: "a", ref: "data.remote.a" },
|
|
301
|
+
{ reader: "c", upstream: "b", ref: "data.remote.b" },
|
|
302
|
+
] }]);
|
|
303
|
+
const left = narrowGroups({ ...g, order: ["b", "c"], waves: [["b", "c"]], groups: undefined }, components, g.groups);
|
|
304
|
+
expect(left.groups).toEqual([{ capability: "fake-group", components: ["b", "c"], reads: [{ reader: "c", upstream: "b", ref: "data.remote.b" }] }]);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe("core stays lexicon-free here (ws-107)", () => {
|
|
309
|
+
const read = (file: string) => readFileSync(join(import.meta.dirname, file), "utf-8");
|
|
310
|
+
const imports = (source: string) => [...source.matchAll(/^import[^;]*?from\s+"([^"]+)"/gms)].map((m) => m[1]!);
|
|
311
|
+
|
|
312
|
+
test("the group modules import no lexicon, and groups.ts names none", () => {
|
|
313
|
+
for (const file of ["groups.ts", "capability.ts", "pr-run.ts", "fan-out-run.ts", "fan-out.ts"]) {
|
|
314
|
+
const from = imports(read(file));
|
|
315
|
+
expect(from.length, file).toBeGreaterThan(0);
|
|
316
|
+
expect(from.filter((m) => /lexicon|^@intentius\/chant-lexicon|lexicons\//.test(m)), file).toEqual([]);
|
|
317
|
+
}
|
|
318
|
+
expect(read("groups.ts")).not.toMatch(/terraform|tofu|choudoufu|cloudformation|\bcfn\b|kubernetes|\baws\b|\bazure\b|\bgcp\b/i);
|
|
319
|
+
});
|
|
320
|
+
});
|