@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
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
type DriverGate,
|
|
40
40
|
type DriverPhase,
|
|
41
41
|
type DriverRunResult,
|
|
42
|
+
type InterpretGroups,
|
|
42
43
|
} from "./driver";
|
|
43
44
|
import type { PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
44
45
|
import type { GateDigestMismatch, GateLedgerPort } from "../op/gate";
|
|
@@ -51,7 +52,10 @@ import { existsSync, statSync } from "node:fs";
|
|
|
51
52
|
import { getHeadCommit } from "../lifecycle/git";
|
|
52
53
|
import { buildCapabilityRegistry } from "./capability-plugin-loader";
|
|
53
54
|
import type { CapabilityRegistry } from "./capability";
|
|
55
|
+
import type { FanOutPlan } from "./fan-out";
|
|
56
|
+
import { componentSetFanOut, formRunGroups, planAndRunComponentGroup } from "./groups";
|
|
54
57
|
import { applyConfigDefaults } from "./config-defaults";
|
|
58
|
+
import { withDeclaredReads } from "./declared-reads";
|
|
55
59
|
import { loadChantConfig, type ChantConfig } from "../config";
|
|
56
60
|
|
|
57
61
|
/** One component in `chant list --components` output. */
|
|
@@ -510,6 +514,8 @@ export interface RunComponentsResult {
|
|
|
510
514
|
};
|
|
511
515
|
/** This run's resolved build-time parameters (chant #1108) — the component-driver counterpart of `../cli/commands/build.ts`'s `BuildResult.buildParams`. Present only once the run actually reached dispatch (mirrors `BuildResult.buildParams`, which is likewise absent on an early-error return). */
|
|
512
516
|
buildParams?: BuildParamProvenance[];
|
|
517
|
+
/** The grouped fan-out the run applied (ws-107, #4465), when a group formed or a component fell back from one. */
|
|
518
|
+
grouped?: FanOutPlan;
|
|
513
519
|
}
|
|
514
520
|
|
|
515
521
|
/**
|
|
@@ -545,6 +551,13 @@ export interface ResolvedComponentTargets {
|
|
|
545
551
|
targets: DriverComponent[];
|
|
546
552
|
/** For a list selector (#3950): the dependencies the closure added that the list did not name, in discovery order. */
|
|
547
553
|
added?: string[];
|
|
554
|
+
/**
|
|
555
|
+
* For a list selector: the names the list gave, and every enabled component
|
|
556
|
+
* the closure was taken over (#4516). `runComponents` takes the closure again
|
|
557
|
+
* once the declared reads are written in, so a listed importer pulls in the
|
|
558
|
+
* component whose export it reads.
|
|
559
|
+
*/
|
|
560
|
+
list?: { named: string[]; enabled: DriverComponent[] };
|
|
548
561
|
/** Component name to the environments it declares (#3153), for the components that declare any (#4330). */
|
|
549
562
|
sites?: Record<string, ComponentEnvironmentDeclaration[]>;
|
|
550
563
|
error?: string;
|
|
@@ -650,6 +663,14 @@ function resolveComponentList(
|
|
|
650
663
|
}
|
|
651
664
|
|
|
652
665
|
const enabled = withoutDisabled(discovered, all);
|
|
666
|
+
return { success: true, ...dependencyClosure(names, enabled), list: { named: names, enabled } };
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* `names` and every component of `enabled` they depend on, transitively, in
|
|
671
|
+
* `enabled`'s order, and which of those the names did not give (#3950).
|
|
672
|
+
*/
|
|
673
|
+
export function dependencyClosure(names: readonly string[], enabled: readonly DriverComponent[]): { targets: DriverComponent[]; added: string[] } {
|
|
653
674
|
const byName = new Map(enabled.map((c) => [c.name, c]));
|
|
654
675
|
const included = new Set<string>();
|
|
655
676
|
const visit = (name: string): void => {
|
|
@@ -662,7 +683,7 @@ function resolveComponentList(
|
|
|
662
683
|
|
|
663
684
|
const targets = enabled.filter((c) => included.has(c.name));
|
|
664
685
|
const named = new Set(names);
|
|
665
|
-
return {
|
|
686
|
+
return { targets, added: targets.map((c) => c.name).filter((n) => !named.has(n)) };
|
|
666
687
|
}
|
|
667
688
|
|
|
668
689
|
export async function runComponents(
|
|
@@ -674,13 +695,6 @@ export async function runComponents(
|
|
|
674
695
|
if (!resolved.success) {
|
|
675
696
|
return { success: false, selected: [], error: resolved.error };
|
|
676
697
|
}
|
|
677
|
-
const targets = resolved.targets;
|
|
678
|
-
const narrowed = options.targets ?? {};
|
|
679
|
-
const outside = Object.keys(narrowed).filter((name) => !targets.some((c) => c.name === name));
|
|
680
|
-
if (outside.length > 0) {
|
|
681
|
-
return { success: false, selected: [], error: `--target names ${outside.join(", ")}, which this run does not run` };
|
|
682
|
-
}
|
|
683
|
-
const targeted = Object.keys(narrowed).length > 0 ? { targets: narrowed } : {};
|
|
684
698
|
|
|
685
699
|
// (#629) Resolve `chant.config.ts`'s `sbom`/`signing`/`vulnPolicy` sections
|
|
686
700
|
// and fill their defaults into every recognized step (`generate-sbom`,
|
|
@@ -700,8 +714,27 @@ export async function runComponents(
|
|
|
700
714
|
lexicons: lexiconNames(config.lexicons),
|
|
701
715
|
}));
|
|
702
716
|
const env = options.env ?? "local";
|
|
703
|
-
|
|
704
|
-
|
|
717
|
+
// #4516: the reads a capability finds in its steps' files (a template's
|
|
718
|
+
// Fn::ImportValue) order a set run as `deriveFanOut` orders a fan-out, so an
|
|
719
|
+
// importer runs after the component whose export it reads. A list takes its
|
|
720
|
+
// closure again with them written in, so a listed importer pulls in its
|
|
721
|
+
// exporter. A single name runs on its own, against seeded outputs.
|
|
722
|
+
let resolvedTargets: DriverComponent[];
|
|
723
|
+
let added = resolved.added ?? [];
|
|
724
|
+
if (resolved.list) {
|
|
725
|
+
const enabled = await withDeclaredReads(resolved.list.enabled.map((component) => applyConfigDefaults(component, config)), registry, path);
|
|
726
|
+
({ targets: resolvedTargets, added } = dependencyClosure(resolved.list.named, enabled));
|
|
727
|
+
} else {
|
|
728
|
+
const defaulted = resolved.targets.map((component) => applyConfigDefaults(component, config));
|
|
729
|
+
resolvedTargets = selector === "all" ? await withDeclaredReads(defaulted, registry, path) : defaulted;
|
|
730
|
+
}
|
|
731
|
+
const selected = resolvedTargets.map((c) => c.name);
|
|
732
|
+
const narrowed = options.targets ?? {};
|
|
733
|
+
const outside = Object.keys(narrowed).filter((name) => !selected.includes(name));
|
|
734
|
+
if (outside.length > 0) {
|
|
735
|
+
return { success: false, selected: [], error: `--target names ${outside.join(", ")}, which this run does not run` };
|
|
736
|
+
}
|
|
737
|
+
const targeted = Object.keys(narrowed).length > 0 ? { targets: narrowed } : {};
|
|
705
738
|
// Cross-component/cross-stack outputs to resolve references against. Seeded
|
|
706
739
|
// from a caller (a downstream CI job passing an upstream job's dumped
|
|
707
740
|
// outputs via `--seed-outputs`); grows as this run's components complete.
|
|
@@ -719,16 +752,38 @@ export async function runComponents(
|
|
|
719
752
|
|
|
720
753
|
try {
|
|
721
754
|
if (selector === "all" || isComponentList(selector)) {
|
|
755
|
+
// ws-107 (#4465): the components a capability takes as one plan in their
|
|
756
|
+
// wave and apply as one group, by the rule the pull-request run uses.
|
|
757
|
+
// A cycle or unknown dependency throws here the error the driver would:
|
|
758
|
+
// `componentSetFanOut` drops a dependency outside the set, so the full
|
|
759
|
+
// graph is resolved first.
|
|
760
|
+
resolveComponentGraph(resolvedTargets);
|
|
761
|
+
const grouped = await formRunGroups({ components: resolvedTargets, plan: componentSetFanOut(resolvedTargets), registry, env });
|
|
722
762
|
if (isComponentList(selector) && options.onPlan) {
|
|
723
763
|
// Resolved here, before dispatch, so the order is printed before any
|
|
724
|
-
// component runs.
|
|
725
|
-
|
|
726
|
-
const graph = resolveComponentGraph(resolvedTargets);
|
|
727
|
-
options.onPlan({ order: graph.order, waves: graph.waves, added: resolved.added ?? [] });
|
|
764
|
+
// component runs.
|
|
765
|
+
options.onPlan({ order: grouped.order, waves: grouped.waves.filter((w) => w.length > 0), added });
|
|
728
766
|
}
|
|
767
|
+
const byName = new Map(resolvedTargets.map((c) => [c.name, c]));
|
|
768
|
+
const groups: InterpretGroups | undefined = grouped.groups?.length
|
|
769
|
+
? {
|
|
770
|
+
waves: grouped.waves,
|
|
771
|
+
groups: grouped.groups,
|
|
772
|
+
run: (group, componentOutputs) =>
|
|
773
|
+
planAndRunComponentGroup({
|
|
774
|
+
group,
|
|
775
|
+
components: byName,
|
|
776
|
+
registry,
|
|
777
|
+
env,
|
|
778
|
+
ctxOf: (name) => deployContext({ env, releaseIdentity, ...targeted }, name),
|
|
779
|
+
componentOutputs,
|
|
780
|
+
}),
|
|
781
|
+
}
|
|
782
|
+
: undefined;
|
|
729
783
|
const run = await runInterpretDriver(resolvedTargets, registry, {
|
|
730
784
|
env,
|
|
731
785
|
releaseIdentity,
|
|
786
|
+
...(groups ? { groups } : {}),
|
|
732
787
|
...targeted,
|
|
733
788
|
componentOutputs: seedOutputs,
|
|
734
789
|
...(emit ? { onRunEvent: emit } : {}),
|
|
@@ -740,6 +795,7 @@ export async function runComponents(
|
|
|
740
795
|
selected,
|
|
741
796
|
components: resolvedTargets,
|
|
742
797
|
buildParams: options.buildParams,
|
|
798
|
+
...(grouped.groups?.length || grouped.fallbacks?.length ? { grouped } : {}),
|
|
743
799
|
...(run.status === "gated" && run.gate
|
|
744
800
|
? {
|
|
745
801
|
gated: {
|
|
@@ -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` ──────────────────────
|
|
@@ -436,6 +439,42 @@ function wiringReferences(input: Record<string, unknown>): Array<WiringReference
|
|
|
436
439
|
return [...found.values()].sort((a, b) => a.ref.localeCompare(b.ref));
|
|
437
440
|
}
|
|
438
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
|
+
|
|
439
478
|
/**
|
|
440
479
|
* The cross-component references in `input` that resolve to another value
|
|
441
480
|
* against `now` than against `read`, sorted (#3999). A pull request's apply
|
|
@@ -1148,6 +1187,27 @@ export interface InterpretRunOptions {
|
|
|
1148
1187
|
releaseIdentity?: (component: string) => ReleaseIdentity | undefined;
|
|
1149
1188
|
/** The addresses each component narrows to (#4300), by component: its `DeployContext.targets`. */
|
|
1150
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> }>;
|
|
1151
1211
|
}
|
|
1152
1212
|
|
|
1153
1213
|
/**
|
|
@@ -1219,8 +1279,20 @@ export async function runInterpretDriver(
|
|
|
1219
1279
|
registry: CapabilityRegistry,
|
|
1220
1280
|
options: InterpretRunOptions,
|
|
1221
1281
|
): Promise<DriverRunResult> {
|
|
1222
|
-
const
|
|
1282
|
+
const graph = resolveComponentGraph(components);
|
|
1223
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> = {};
|
|
1224
1296
|
const componentOutputs: Record<string, Record<string, unknown>> = { ...(options.componentOutputs ?? {}) };
|
|
1225
1297
|
const emit = joinRunEventSinks(options.onRunEvent, progressSinkOf(options.onProgress));
|
|
1226
1298
|
const gates: GateContext = {
|
|
@@ -1239,8 +1311,19 @@ export async function runInterpretDriver(
|
|
|
1239
1311
|
waveLoop: for (const [waveIndex, wave] of waves.entries()) {
|
|
1240
1312
|
const waveNum = waveIndex + 1;
|
|
1241
1313
|
emit?.({ type: "wave-start", wave: waveNum, components: wave });
|
|
1242
|
-
const waveComponents = wave.map((name) => byName.get(name)!);
|
|
1243
|
-
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(
|
|
1244
1327
|
waveComponents.map(async (component) => {
|
|
1245
1328
|
emit?.({ type: "component-start", wave: waveNum, component: component.name });
|
|
1246
1329
|
const result = await runComponentDeploy(
|
|
@@ -1256,6 +1339,7 @@ export async function runInterpretDriver(
|
|
|
1256
1339
|
return result;
|
|
1257
1340
|
}),
|
|
1258
1341
|
);
|
|
1342
|
+
const waveResults = [...ownResults, ...(await groupResults).flat()];
|
|
1259
1343
|
results.push(...waveResults);
|
|
1260
1344
|
const stopped = waveResults.find((r) => r.status !== "ok");
|
|
1261
1345
|
emit?.({ type: "wave-settle", wave: waveNum, status: progressStatus(stopped?.status ?? "ok") });
|
|
@@ -1274,6 +1358,7 @@ export async function runInterpretDriver(
|
|
|
1274
1358
|
emit?.({ type: "run-settle", status: progressStatus(status), durationMs: null });
|
|
1275
1359
|
const result: DriverRunResult = {
|
|
1276
1360
|
order, waves, results, ok, status, failedComponent, componentOutputs,
|
|
1361
|
+
...(Object.keys(groupOutcomes).length > 0 ? { groupOutcomes } : {}),
|
|
1277
1362
|
...(gated
|
|
1278
1363
|
? {
|
|
1279
1364
|
gatedComponent: gated.component,
|