@intentius/chant 0.124.51 → 0.124.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/discover.js +3 -1
- package/dist/behaviour.d.ts +1 -1
- package/dist/behaviour.d.ts.map +1 -1
- package/dist/capabilities/templates.d.ts +65 -0
- package/dist/capabilities/templates.d.ts.map +1 -0
- package/dist/capabilities/templates.js +193 -0
- package/dist/capabilities/templates.json +145 -0
- package/dist/change-set-graph.d.ts +35 -3
- package/dist/change-set-graph.d.ts.map +1 -1
- package/dist/change-set-graph.js +60 -9
- package/dist/change-set.d.ts +65 -7
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +23 -12
- package/dist/ci-identity.d.ts +7 -2
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +4 -1
- package/dist/cli/commands/import-index.d.ts +52 -0
- package/dist/cli/commands/import-index.d.ts.map +1 -0
- package/dist/cli/commands/import-index.js +226 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +4 -2
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/components.js +11 -2
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +16 -3
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +22 -4
- package/dist/cli/handlers/index-cloud.d.ts +14 -0
- package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
- package/dist/cli/handlers/index-cloud.js +22 -0
- package/dist/cli/handlers/index-diff.d.ts +18 -0
- package/dist/cli/handlers/index-diff.d.ts.map +1 -0
- package/dist/cli/handlers/index-diff.js +142 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-drift.js +64 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +188 -28
- package/dist/cli/handlers/misc.d.ts +9 -0
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +77 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +17 -3
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +89 -15
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +28 -4
- package/dist/cli/handlers/scenario.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.js +8 -7
- package/dist/cli/handlers/search.d.ts +34 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +216 -36
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +55 -1
- package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
- package/dist/cli/mcp/lifecycle-tools.js +3 -2
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.js +4 -3
- package/dist/cli/mcp/workspace-tools.js +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +55 -2
- package/dist/cli/registry.d.ts +19 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cloud-lister/aws.d.ts +35 -0
- package/dist/cloud-lister/aws.d.ts.map +1 -0
- package/dist/cloud-lister/aws.js +203 -0
- package/dist/cloud-lister/azure.d.ts +34 -0
- package/dist/cloud-lister/azure.d.ts.map +1 -0
- package/dist/cloud-lister/azure.js +129 -0
- package/dist/cloud-lister/common.d.ts +40 -0
- package/dist/cloud-lister/common.d.ts.map +1 -0
- package/dist/cloud-lister/common.js +83 -0
- package/dist/cloud-lister/credentials.d.ts +50 -0
- package/dist/cloud-lister/credentials.d.ts.map +1 -0
- package/dist/cloud-lister/credentials.js +86 -0
- package/dist/cloud-lister/fakes.d.ts +27 -0
- package/dist/cloud-lister/fakes.d.ts.map +1 -0
- package/dist/cloud-lister/fakes.js +50 -0
- package/dist/cloud-lister/gcp.d.ts +42 -0
- package/dist/cloud-lister/gcp.d.ts.map +1 -0
- package/dist/cloud-lister/gcp.js +143 -0
- package/dist/cloud-lister/guard.d.ts +49 -0
- package/dist/cloud-lister/guard.d.ts.map +1 -0
- package/dist/cloud-lister/guard.js +103 -0
- package/dist/cloud-lister/ids.d.ts +23 -0
- package/dist/cloud-lister/ids.d.ts.map +1 -0
- package/dist/cloud-lister/ids.js +42 -0
- package/dist/cloud-lister/index.d.ts +67 -0
- package/dist/cloud-lister/index.d.ts.map +1 -0
- package/dist/cloud-lister/index.js +102 -0
- package/dist/cloud-lister/kubernetes.d.ts +64 -0
- package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
- package/dist/cloud-lister/kubernetes.js +276 -0
- package/dist/cloud-lister/markers.d.ts +31 -0
- package/dist/cloud-lister/markers.d.ts.map +1 -0
- package/dist/cloud-lister/markers.js +73 -0
- package/dist/cloud-lister/mask.d.ts +31 -0
- package/dist/cloud-lister/mask.d.ts.map +1 -0
- package/dist/cloud-lister/mask.js +74 -0
- package/dist/cloud-lister/sigv4.d.ts +29 -0
- package/dist/cloud-lister/sigv4.d.ts.map +1 -0
- package/dist/cloud-lister/sigv4.js +54 -0
- package/dist/cloud-lister/transport.d.ts +55 -0
- package/dist/cloud-lister/transport.d.ts.map +1 -0
- package/dist/cloud-lister/transport.js +86 -0
- package/dist/cloud-lister/types.d.ts +121 -0
- package/dist/cloud-lister/types.d.ts.map +1 -0
- package/dist/cloud-lister/types.js +24 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +6 -0
- package/dist/components/capability-plugin.d.ts +9 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability.d.ts +224 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +21 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +58 -14
- package/dist/components/covered-round.d.ts +173 -0
- package/dist/components/covered-round.d.ts.map +1 -0
- package/dist/components/covered-round.js +200 -0
- package/dist/components/declared-reads.d.ts +38 -0
- package/dist/components/declared-reads.d.ts.map +1 -0
- package/dist/components/declared-reads.js +131 -0
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/deploy-units.js +4 -0
- package/dist/components/driver.d.ts +42 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +74 -3
- package/dist/components/fan-out-run.d.ts +23 -1
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +82 -14
- package/dist/components/fan-out-support.d.ts +6 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +4 -1
- package/dist/components/fan-out.d.ts +28 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/groups.d.ts +229 -0
- package/dist/components/groups.d.ts.map +1 -0
- package/dist/components/groups.js +760 -0
- package/dist/components/index.d.ts +7 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +5 -0
- package/dist/components/plan-shape-conformance.d.ts +48 -0
- package/dist/components/plan-shape-conformance.d.ts.map +1 -0
- package/dist/components/plan-shape-conformance.js +122 -0
- package/dist/components/pr-run.d.ts +62 -7
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +354 -53
- package/dist/components/promote-rounds.d.ts +9 -0
- package/dist/components/promote-rounds.d.ts.map +1 -1
- package/dist/components/promote-rounds.js +23 -2
- package/dist/components/promote.d.ts +17 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +36 -6
- package/dist/components/release-gate.d.ts +23 -1
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +97 -33
- package/dist/components/run-groups.d.ts +58 -0
- package/dist/components/run-groups.d.ts.map +1 -0
- package/dist/components/run-groups.js +78 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +6 -1
- package/dist/components/verbs/index.d.ts +1 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +1 -0
- package/dist/components/verbs/member-outputs.d.ts +187 -0
- package/dist/components/verbs/member-outputs.d.ts.map +1 -0
- package/dist/components/verbs/member-outputs.js +218 -0
- package/dist/components/wave-plan.d.ts +2 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +4 -1
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -0
- package/dist/deep-observation.d.ts +1 -13
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/deep-observation.js +5 -22
- package/dist/drift-record.d.ts +542 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +886 -0
- package/dist/engine-facts.d.ts +144 -0
- package/dist/engine-facts.d.ts.map +1 -0
- package/dist/engine-facts.js +14 -0
- package/dist/graph-ir.d.ts +153 -2
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +224 -6
- package/dist/graph-refs.d.ts +145 -0
- package/dist/graph-refs.d.ts.map +1 -1
- package/dist/graph-refs.js +305 -0
- package/dist/graph-schema/fixtures/adopt.json +15 -0
- package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/dist/graph-schema/fixtures/index.json +68 -0
- package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/dist/graph-schema/fixtures.d.ts +23 -0
- package/dist/graph-schema/fixtures.d.ts.map +1 -0
- package/dist/graph-schema/fixtures.js +246 -0
- package/dist/graph-schema/generate.d.ts +15 -0
- package/dist/graph-schema/generate.d.ts.map +1 -0
- package/dist/graph-schema/generate.js +267 -0
- package/dist/graph-schema/graph.schema.json +1387 -0
- package/dist/health.schema.json +2 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/instance-nodes.d.ts +159 -0
- package/dist/instance-nodes.d.ts.map +1 -0
- package/dist/instance-nodes.js +231 -0
- package/dist/lexicon.d.ts +72 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/adoption-file.d.ts +56 -0
- package/dist/lifecycle/adoption-file.d.ts.map +1 -0
- package/dist/lifecycle/adoption-file.js +123 -0
- package/dist/lifecycle/cloud-index.d.ts +186 -0
- package/dist/lifecycle/cloud-index.d.ts.map +1 -0
- package/dist/lifecycle/cloud-index.js +618 -0
- package/dist/lifecycle/derive-edges.d.ts +59 -0
- package/dist/lifecycle/derive-edges.d.ts.map +1 -0
- package/dist/lifecycle/derive-edges.js +126 -0
- package/dist/lifecycle/git.d.ts +10 -9
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +7 -1
- package/dist/lifecycle/identity-match.d.ts +133 -0
- package/dist/lifecycle/identity-match.d.ts.map +1 -0
- package/dist/lifecycle/identity-match.js +147 -0
- package/dist/lifecycle/index-diff.d.ts +224 -0
- package/dist/lifecycle/index-diff.d.ts.map +1 -0
- package/dist/lifecycle/index-diff.js +327 -0
- package/dist/lifecycle/index-document.d.ts +130 -0
- package/dist/lifecycle/index-document.d.ts.map +1 -0
- package/dist/lifecycle/index-document.js +189 -0
- package/dist/lifecycle/index-drift.d.ts +121 -0
- package/dist/lifecycle/index-drift.d.ts.map +1 -0
- package/dist/lifecycle/index-drift.js +178 -0
- package/dist/lifecycle/index-import.d.ts +168 -0
- package/dist/lifecycle/index-import.d.ts.map +1 -0
- package/dist/lifecycle/index-import.js +515 -0
- package/dist/lifecycle/index-join.d.ts +129 -0
- package/dist/lifecycle/index-join.d.ts.map +1 -0
- package/dist/lifecycle/index-join.js +248 -0
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/observation-baseline.d.ts +61 -21
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/observation-baseline.js +144 -19
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +6 -1
- package/dist/lifecycle/query.d.ts +45 -4
- package/dist/lifecycle/query.d.ts.map +1 -1
- package/dist/lifecycle/query.js +89 -7
- package/dist/lifecycle/replay.d.ts +10 -3
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/replay.js +10 -6
- package/dist/lifecycle/scenario.d.ts +4 -3
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
- package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-fake.js +21 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-s3.js +254 -0
- package/dist/lifecycle/snapshot-store.d.ts +170 -0
- package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store.js +832 -0
- package/dist/lifecycle/snapshot.d.ts +11 -0
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +18 -5
- package/dist/lifecycle/type-listing.d.ts +148 -0
- package/dist/lifecycle/type-listing.d.ts.map +1 -0
- package/dist/lifecycle/type-listing.js +166 -0
- package/dist/lint/component-checks.d.ts +15 -1
- package/dist/lint/component-checks.d.ts.map +1 -1
- package/dist/lint/component-checks.js +1 -0
- package/dist/lint/presets/relaxed.json +2 -1
- package/dist/lint/presets/strict.json +2 -1
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
- package/dist/lint/rules/comp/index.d.ts +2 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/comp/index.js +4 -1
- package/dist/observation.d.ts +19 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +18 -1
- package/dist/op/activities/activity-contracts.d.ts +1 -1
- package/dist/op/activities/activity-contracts.js +1 -1
- package/dist/op/activities/change-set.d.ts +6 -4
- package/dist/op/activities/change-set.d.ts.map +1 -1
- package/dist/op/activities/change-set.js +4 -2
- package/dist/op/activity-failure.d.ts +27 -0
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-failure.js +31 -0
- package/dist/op/index.d.ts +2 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +5 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -8
- package/dist/pr-loop.d.ts +92 -3
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +62 -3
- package/dist/pulumi-urn.d.ts +101 -0
- package/dist/pulumi-urn.d.ts.map +1 -0
- package/dist/pulumi-urn.js +131 -0
- package/dist/release-floors.d.ts +23 -0
- package/dist/release-floors.d.ts.map +1 -0
- package/dist/release-floors.js +22 -0
- package/dist/sensitive-key.d.ts +23 -0
- package/dist/sensitive-key.d.ts.map +1 -0
- package/dist/sensitive-key.js +31 -0
- package/dist/wave-runtime.mjs +234 -234
- package/dist/workspace/adoption-report.d.ts +53 -0
- package/dist/workspace/adoption-report.d.ts.map +1 -0
- package/dist/workspace/adoption-report.js +76 -0
- package/dist/workspace/capabilities-contract.d.ts +7 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -1
- package/dist/workspace/capabilities-contract.js +2 -1
- package/dist/workspace/capabilities.schema.json +98 -0
- package/dist/workspace/change-set.schema.json +42 -3
- package/dist/workspace/ci-cloud-identity.d.ts +13 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +30 -1
- package/dist/workspace/compose-graph.d.ts +10 -1
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +6 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +6 -1
- package/dist/workspace/drift-cli.d.ts +81 -0
- package/dist/workspace/drift-cli.d.ts.map +1 -0
- package/dist/workspace/drift-cli.js +317 -0
- package/dist/workspace/drift.schema.json +672 -0
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +3 -0
- package/dist/workspace/graph.schema.json +72 -0
- package/dist/workspace/init.d.ts +5 -1
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/init.js +55 -9
- package/dist/workspace/ledger.schema.json +1 -1
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +2 -0
- package/dist/workspace/pr-report.schema.json +76 -3
- package/dist/workspace/promote-waves.d.ts +112 -0
- package/dist/workspace/promote-waves.d.ts.map +1 -0
- package/dist/workspace/promote-waves.js +290 -0
- package/dist/workspace/promote.d.ts +84 -2
- package/dist/workspace/promote.d.ts.map +1 -1
- package/dist/workspace/promote.js +347 -61
- package/dist/workspace/reason-codes.d.ts +12 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +14 -0
- package/dist/workspace/search-contract.d.ts +3 -3
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/search-contract.js +2 -0
- package/dist/workspace/search.schema.json +103 -5
- package/package.json +14 -1
- package/src/__fixtures__/drift-record/README.md +58 -0
- package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
- package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
- package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
- package/src/__fixtures__/drift-record/component-status.json +91 -0
- package/src/__fixtures__/drift-record/deep-diff.json +259 -0
- package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
- package/src/__fixtures__/drift-record/live-diff.json +125 -0
- package/src/audit/discover.e2e.test.ts +14 -0
- package/src/audit/discover.ts +3 -1
- package/src/behaviour.ts +1 -1
- package/src/capabilities/templates.json +145 -0
- package/src/capabilities/templates.test.ts +212 -0
- package/src/capabilities/templates.ts +204 -0
- package/src/change-set-bundle.test.ts +83 -0
- package/src/change-set-graph.ts +80 -9
- package/src/change-set.test.ts +24 -0
- package/src/change-set.ts +77 -14
- package/src/ci-identity-targets.test.ts +23 -0
- package/src/ci-identity.ts +10 -4
- package/src/cli/commands/import-index.test.ts +117 -0
- package/src/cli/commands/import-index.ts +265 -0
- package/src/cli/commands/lint.ts +4 -2
- package/src/cli/handlers/components.test.ts +9 -0
- package/src/cli/handlers/components.ts +12 -2
- package/src/cli/handlers/fan-out.ts +17 -3
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +26 -4
- package/src/cli/handlers/index-cloud.ts +32 -0
- package/src/cli/handlers/index-diff.test.ts +68 -0
- package/src/cli/handlers/index-diff.ts +146 -0
- package/src/cli/handlers/ledger-root-member.test.ts +161 -0
- package/src/cli/handlers/lifecycle-drift.ts +68 -0
- package/src/cli/handlers/lifecycle.test.ts +33 -11
- package/src/cli/handlers/lifecycle.ts +205 -30
- package/src/cli/handlers/misc.ts +73 -0
- package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
- package/src/cli/handlers/pr.ts +16 -3
- package/src/cli/handlers/promote.test.ts +64 -0
- package/src/cli/handlers/promote.ts +81 -6
- package/src/cli/handlers/query.ts +27 -4
- package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
- package/src/cli/handlers/run-groups-cli.test.ts +229 -0
- package/src/cli/handlers/scenario.test.ts +13 -0
- package/src/cli/handlers/scenario.ts +8 -7
- package/src/cli/handlers/search-index.test.ts +255 -0
- package/src/cli/handlers/search-json.test.ts +1 -1
- package/src/cli/handlers/search-ownership.test.ts +194 -0
- package/src/cli/handlers/search.ts +271 -38
- package/src/cli/main.test.ts +7 -0
- package/src/cli/main.ts +56 -1
- package/src/cli/mcp/lifecycle-tools.ts +3 -2
- package/src/cli/mcp/resource-handlers.ts +4 -3
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/cli/parse-args.ts +40 -2
- package/src/cli/registry.ts +19 -1
- package/src/cloud-lister/aws.test.ts +202 -0
- package/src/cloud-lister/aws.ts +226 -0
- package/src/cloud-lister/azure.test.ts +132 -0
- package/src/cloud-lister/azure.ts +142 -0
- package/src/cloud-lister/common.ts +115 -0
- package/src/cloud-lister/credentials.ts +110 -0
- package/src/cloud-lister/fakes.ts +66 -0
- package/src/cloud-lister/gcp.test.ts +121 -0
- package/src/cloud-lister/gcp.ts +159 -0
- package/src/cloud-lister/guard.test.ts +198 -0
- package/src/cloud-lister/guard.ts +111 -0
- package/src/cloud-lister/ids.ts +54 -0
- package/src/cloud-lister/index.ts +134 -0
- package/src/cloud-lister/kubernetes.test.ts +160 -0
- package/src/cloud-lister/kubernetes.ts +284 -0
- package/src/cloud-lister/markers.ts +74 -0
- package/src/cloud-lister/mask.ts +77 -0
- package/src/cloud-lister/shape.test.ts +81 -0
- package/src/cloud-lister/sigv4.ts +70 -0
- package/src/cloud-lister/transport.ts +118 -0
- package/src/cloud-lister/types.ts +135 -0
- package/src/components/capability-plugin-loader.ts +4 -0
- package/src/components/capability-plugin.ts +10 -0
- package/src/components/capability.ts +238 -0
- package/src/components/cli-support.ts +70 -14
- package/src/components/covered-round.test.ts +166 -0
- package/src/components/covered-round.ts +293 -0
- package/src/components/declared-reads.test.ts +103 -0
- package/src/components/declared-reads.ts +134 -0
- package/src/components/deploy-units.ts +4 -0
- package/src/components/driver.ts +101 -4
- package/src/components/fan-out-run.ts +95 -15
- package/src/components/fan-out-support.ts +10 -1
- package/src/components/fan-out.ts +30 -0
- package/src/components/groups.test.ts +320 -0
- package/src/components/groups.ts +880 -0
- package/src/components/index.ts +59 -0
- package/src/components/plan-shape-conformance.ts +130 -0
- package/src/components/pr-env-run-bundle.test.ts +2 -0
- package/src/components/pr-run.test.ts +728 -4
- package/src/components/pr-run.ts +395 -55
- package/src/components/promote-rounds.ts +25 -2
- package/src/components/promote.ts +55 -5
- package/src/components/registry.test.ts +1 -1
- package/src/components/release-gate.ts +115 -30
- package/src/components/release-groups.test.ts +362 -0
- package/src/components/run-groups.ts +113 -0
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +1 -0
- package/src/components/verbs/member-outputs.test.ts +205 -0
- package/src/components/verbs/member-outputs.ts +308 -0
- package/src/components/wave-plan.ts +5 -2
- package/src/config.ts +44 -0
- package/src/deep-observation.ts +5 -23
- package/src/drift-record.test.ts +596 -0
- package/src/drift-record.ts +1206 -0
- package/src/engine-facts.guard.test.ts +111 -0
- package/src/engine-facts.test.ts +178 -0
- package/src/engine-facts.ts +154 -0
- package/src/gated-waves-bundle.test.ts +2 -0
- package/src/graph-ir-live.test.ts +92 -0
- package/src/graph-ir.ts +317 -7
- package/src/graph-refs.ts +416 -0
- package/src/graph-schema/fixtures/adopt.json +15 -0
- package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/src/graph-schema/fixtures/index.json +68 -0
- package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/src/graph-schema/fixtures.ts +267 -0
- package/src/graph-schema/generate.ts +263 -0
- package/src/graph-schema/graph-schema.test.ts +153 -0
- package/src/graph-schema/graph.schema.json +1387 -0
- package/src/health.schema.json +2 -1
- package/src/identity.ts +2 -0
- package/src/index.ts +2 -0
- package/src/instance-nodes.ts +288 -0
- package/src/lexicon.ts +76 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
- package/src/lifecycle/adoption-file.test.ts +100 -0
- package/src/lifecycle/adoption-file.ts +154 -0
- package/src/lifecycle/cloud-index.test.ts +517 -0
- package/src/lifecycle/cloud-index.ts +736 -0
- package/src/lifecycle/derive-edges.test.ts +193 -0
- package/src/lifecycle/derive-edges.ts +182 -0
- package/src/lifecycle/git.ts +10 -4
- package/src/lifecycle/identity-match.test.ts +308 -0
- package/src/lifecycle/identity-match.ts +257 -0
- package/src/lifecycle/index-diff.test.ts +198 -0
- package/src/lifecycle/index-diff.ts +476 -0
- package/src/lifecycle/index-document.test.ts +64 -0
- package/src/lifecycle/index-document.ts +299 -0
- package/src/lifecycle/index-drift.test.ts +191 -0
- package/src/lifecycle/index-drift.ts +304 -0
- package/src/lifecycle/index-import.test.ts +255 -0
- package/src/lifecycle/index-import.ts +634 -0
- package/src/lifecycle/index-join.test.ts +250 -0
- package/src/lifecycle/index-join.ts +304 -0
- package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/member-ledger.test.ts +25 -7
- package/src/lifecycle/observation-baseline.test.ts +72 -1
- package/src/lifecycle/observation-baseline.ts +190 -27
- package/src/lifecycle/observe.ts +6 -1
- package/src/lifecycle/query.test.ts +69 -1
- package/src/lifecycle/query.ts +103 -10
- package/src/lifecycle/replay.test.ts +9 -5
- package/src/lifecycle/replay.ts +9 -5
- package/src/lifecycle/scenario.ts +4 -3
- package/src/lifecycle/snapshot-store-fake.ts +29 -0
- package/src/lifecycle/snapshot-store-s3.ts +316 -0
- package/src/lifecycle/snapshot-store.test.ts +385 -0
- package/src/lifecycle/snapshot-store.ts +981 -0
- package/src/lifecycle/snapshot.test.ts +13 -0
- package/src/lifecycle/snapshot.ts +23 -6
- package/src/lifecycle/type-listing.test.ts +166 -0
- package/src/lifecycle/type-listing.ts +309 -0
- package/src/lint/component-checks.ts +17 -1
- package/src/lint/config.test.ts +3 -0
- package/src/lint/presets/relaxed.json +2 -1
- package/src/lint/presets/strict.json +2 -1
- package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
- package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
- package/src/lint/rules/comp/comp.test.ts +2 -1
- package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
- package/src/lint/rules/comp/comp008.test.ts +77 -0
- package/src/lint/rules/comp/index.ts +4 -1
- package/src/observation-edges.test.ts +33 -0
- package/src/observation.ts +36 -1
- package/src/op/activities/activity-contracts.ts +1 -1
- package/src/op/activities/change-set.ts +8 -5
- package/src/op/activity-failure.ts +36 -0
- package/src/op/index.ts +2 -1
- package/src/op/local-executor.test.ts +80 -0
- package/src/op/local-executor.ts +27 -9
- package/src/pr-loop-bundle.test.ts +5 -2
- package/src/pr-loop.test.ts +57 -1
- package/src/pr-loop.ts +154 -6
- package/src/pulumi-urn.ts +151 -0
- package/src/release-floors.ts +22 -0
- package/src/sensitive-key.ts +34 -0
- package/src/workspace/adoption-report.test.ts +61 -0
- package/src/workspace/adoption-report.ts +103 -0
- package/src/workspace/capabilities-contract.test.ts +4 -0
- package/src/workspace/capabilities-contract.ts +9 -1
- package/src/workspace/capabilities.schema.json +98 -0
- package/src/workspace/change-set.schema.json +42 -3
- package/src/workspace/ci-cloud-identity.ts +31 -1
- package/src/workspace/compose-graph.test.ts +12 -0
- package/src/workspace/compose-graph.ts +12 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.ts +6 -1
- package/src/workspace/drift-cli.test.ts +65 -0
- package/src/workspace/drift-cli.ts +365 -0
- package/src/workspace/drift.schema.json +672 -0
- package/src/workspace/graph-cli.ts +5 -0
- package/src/workspace/graph.schema.json +72 -0
- package/src/workspace/init.test.ts +38 -1
- package/src/workspace/init.ts +50 -8
- package/src/workspace/intent-gaps.test.ts +18 -2
- package/src/workspace/ledger.schema.json +1 -1
- package/src/workspace/nested-graph.ts +1 -0
- package/src/workspace/pr-report.schema.json +76 -3
- package/src/workspace/promote-waves.test.ts +296 -0
- package/src/workspace/promote-waves.ts +340 -0
- package/src/workspace/promote.test.ts +158 -4
- package/src/workspace/promote.ts +398 -57
- package/src/workspace/reason-codes.test.ts +4 -1
- package/src/workspace/reason-codes.ts +14 -0
- package/src/workspace/search-contract.ts +2 -0
- package/src/workspace/search.schema.json +103 -5
package/src/components/pr-run.ts
CHANGED
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
* component in the project and not the selection.
|
|
24
24
|
* - the upstream is in the change and its plan cannot say, or says the value
|
|
25
25
|
* is known only after apply: no value. The dependent's members list the
|
|
26
|
-
* reference under `unknownInputs
|
|
27
|
-
*
|
|
26
|
+
* reference under `unknownInputs`. The apply plans it again once the
|
|
27
|
+
* upstream applied, and the approval covers that round when it matches the
|
|
28
|
+
* preview (see `coveredRound`).
|
|
28
29
|
*
|
|
29
30
|
* `--seed-outputs` wins over both. The apply stage's plan reads the same
|
|
30
31
|
* values, so on the same estate it gets the digest the plan stage showed.
|
|
@@ -66,6 +67,14 @@
|
|
|
66
67
|
* says whether the approved change set covers what it planned, by the same
|
|
67
68
|
* test a resume uses ({@link planCoveredBy}). Covered, the wave applies under
|
|
68
69
|
* the pull request's approval; not covered, it needs its own.
|
|
70
|
+
*
|
|
71
|
+
* ## Groups
|
|
72
|
+
*
|
|
73
|
+
* A fan-out with `groups` (ws-107, `./groups.ts`) plans each group with one
|
|
74
|
+
* `planGroup` call at its wave and applies it with one `runGroup` call
|
|
75
|
+
* there. A member's read of another member of its group is computed by that
|
|
76
|
+
* apply: it is no `unknownInputs` entry and holds nothing back. Each member's
|
|
77
|
+
* digest binds the group's, so the one approval binds the composition.
|
|
69
78
|
*/
|
|
70
79
|
|
|
71
80
|
import { composeChangeSet, verifyChangeSetDigest, type ChangeSetDocument, type ChangeSetEntry, type ChangeSetPart } from "../change-set";
|
|
@@ -77,7 +86,9 @@ import {
|
|
|
77
86
|
PR_REPORT_CONTRACT,
|
|
78
87
|
PR_REPORT_SCHEMA_ID,
|
|
79
88
|
type PrApproval,
|
|
89
|
+
type PrGroup,
|
|
80
90
|
type PrMember,
|
|
91
|
+
type PrMemberRead,
|
|
81
92
|
type PrRefusal,
|
|
82
93
|
type PrRound,
|
|
83
94
|
type PrReport,
|
|
@@ -88,17 +99,20 @@ import {
|
|
|
88
99
|
deployContext,
|
|
89
100
|
movedWiringReferences,
|
|
90
101
|
unresolvedWiringReferences,
|
|
102
|
+
wiringReads,
|
|
91
103
|
type DriverComponent,
|
|
92
104
|
type DriverComponentResult,
|
|
93
105
|
} from "./driver";
|
|
94
106
|
import { deployUnits } from "./deploy-units";
|
|
95
107
|
import { runFanOut, type FanOutRunResult } from "./fan-out-run";
|
|
96
|
-
import { remainingFanOut, type ComponentChangeSignal, type FanOutPlan } from "./fan-out";
|
|
108
|
+
import { remainingFanOut, type ComponentChangeSignal, type FanOutGroup, type FanOutGroupFallback, type FanOutPlan } from "./fan-out";
|
|
109
|
+
import { coveredRound, type PlanShape, type RoundCoverage } from "./covered-round";
|
|
97
110
|
import type { PrApplyRecord } from "./fan-out-record";
|
|
98
|
-
import type { CapabilityRegistry, PlannedOutputs } from "./capability";
|
|
111
|
+
import type { CapabilityRegistry, GroupRun, PlannedOutputs } from "./capability";
|
|
112
|
+
import { groupsByComponent, narrowGroups, planComponentGroup, regroupRemaining } from "./groups";
|
|
99
113
|
import type { RunProgressEvent } from "./run-progress";
|
|
100
114
|
import type { RunEventSink } from "../run-event-stream";
|
|
101
|
-
import { capabilitySteps, planWaveComponent, readDeployedOutputs } from "./wave-plan";
|
|
115
|
+
import { capabilitySteps, planWaveComponent, readDeployedOutputs, type WaveComponentPlan } from "./wave-plan";
|
|
102
116
|
import type { PrMigrationFile } from "../pr-migrations";
|
|
103
117
|
|
|
104
118
|
export interface PrPlanSetOptions {
|
|
@@ -145,10 +159,26 @@ export interface PrPlanSet {
|
|
|
145
159
|
* report's `plannedOutputs`.
|
|
146
160
|
*/
|
|
147
161
|
plannedOutputs?: Record<string, string[]>;
|
|
162
|
+
/** The shapes lexicons supplied with their plans (#4419), by member. */
|
|
163
|
+
shapes?: Record<string, PlanShape>;
|
|
164
|
+
/** The groups planned as one (ws-107), in plan order of their first member. Absent when none. */
|
|
165
|
+
groups?: PrPlanGroup[];
|
|
166
|
+
/** The components that could have grouped and did not, with why (ws-107, ruling 1): the fan-out's `fallbacks`. */
|
|
167
|
+
fallbacks?: FanOutGroupFallback[];
|
|
148
168
|
/** Whether any component failed to plan. */
|
|
149
169
|
failed: boolean;
|
|
150
170
|
}
|
|
151
171
|
|
|
172
|
+
/** A group a pull request planned as one (ws-107). */
|
|
173
|
+
export interface PrPlanGroup extends FanOutGroup {
|
|
174
|
+
/** The group digest folded into each member's `planDigest`; `null` when the group failed to plan. */
|
|
175
|
+
digest: string | null;
|
|
176
|
+
/** Why it failed to plan. Every member is then plan-failed with it. */
|
|
177
|
+
error?: string;
|
|
178
|
+
/** What `runGroup` gets back. Like `plans`, it lives in the process that planned. */
|
|
179
|
+
run?: GroupRun;
|
|
180
|
+
}
|
|
181
|
+
|
|
152
182
|
/** A failed component's members: the units its steps target, else the component itself. */
|
|
153
183
|
function failedParts(component: DriverComponent, error: string): ChangeSetPart[] {
|
|
154
184
|
const units = deployUnits(component.deploy).map((u) => u.unit);
|
|
@@ -178,18 +208,25 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
178
208
|
const parts: ChangeSetPart[] = [];
|
|
179
209
|
const componentOf = new Map<string, string>();
|
|
180
210
|
const unknownOf = new Map<string, { unknown: string[]; build: string[] }>();
|
|
211
|
+
const readsBy = new Map<string, PrMemberRead[]>();
|
|
181
212
|
const plans: Record<string, Record<string, unknown>> = {};
|
|
213
|
+
const shapes: Record<string, PlanShape> = {};
|
|
182
214
|
let failed = false;
|
|
183
215
|
const emit = options.onRunEvent;
|
|
184
216
|
emit?.({ type: "phase-start", component: null, phase: PR_PLAN_PHASE });
|
|
217
|
+
// ws-107: each group plans once, in its wave, with one call.
|
|
218
|
+
const groupOf = groupsByComponent(options.plan);
|
|
219
|
+
const groups: PrPlanGroup[] = [];
|
|
185
220
|
|
|
186
221
|
for (const wave of options.plan.waves) {
|
|
187
222
|
// The dependencies' outputs first, each once. One the change plans is
|
|
188
223
|
// read from its plan, which an earlier wave made (#3999). Any other is
|
|
189
224
|
// read from the deployed estate (#4104). A dependency that cannot say
|
|
190
225
|
// leaves its references unresolved, which its dependent's plan reports
|
|
191
|
-
// if it needs them.
|
|
192
|
-
const deps = [
|
|
226
|
+
// if it needs them. A member of the dependent's own group plans with it.
|
|
227
|
+
const deps = [
|
|
228
|
+
...new Set(wave.flatMap((name) => (byName.get(name)?.dependsOn ?? []).filter((dep) => !groupOf.get(name)?.components.includes(dep)))),
|
|
229
|
+
].filter((dep) => !read.has(dep));
|
|
193
230
|
const fromEstate: string[] = [];
|
|
194
231
|
for (const dep of deps) {
|
|
195
232
|
read.add(dep);
|
|
@@ -203,26 +240,60 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
203
240
|
planned[dep] = Object.keys(known).sort();
|
|
204
241
|
}
|
|
205
242
|
Object.assign(deployed, await readDeployedOutputs(fromEstate, byName, options.registry, (dep) => deployContext(options, dep), outputs));
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
243
|
+
const settle = (name: string, start: number, p: WaveComponentPlan): void => {
|
|
244
|
+
emit?.({
|
|
245
|
+
type: "step-settle",
|
|
246
|
+
component: name,
|
|
247
|
+
phase: PR_PLAN_PHASE,
|
|
248
|
+
step: "plan",
|
|
249
|
+
status: "error" in p ? "failed" : "ok",
|
|
250
|
+
durationMs: Date.now() - start,
|
|
251
|
+
...("error" in p ? { error: p.error } : {}),
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
const groupsHere = [...new Set(wave.map((name) => groupOf.get(name)).filter((g): g is FanOutGroup => g !== undefined))];
|
|
255
|
+
const results = (
|
|
256
|
+
await Promise.all([
|
|
257
|
+
...wave
|
|
258
|
+
.filter((name) => !groupOf.has(name))
|
|
259
|
+
.map(async (name) => {
|
|
260
|
+
emit?.({ type: "step-start", component: name, phase: PR_PLAN_PHASE, step: "plan" });
|
|
261
|
+
const start = Date.now();
|
|
262
|
+
const p = await planWaveComponent(byName.get(name)!, options.registry, deployContext(options, name), outputs);
|
|
263
|
+
settle(name, start, p);
|
|
264
|
+
return [p];
|
|
265
|
+
}),
|
|
266
|
+
...groupsHere.map(async (group) => {
|
|
267
|
+
for (const name of group.components) emit?.({ type: "step-start", component: name, phase: PR_PLAN_PHASE, step: "plan" });
|
|
268
|
+
const start = Date.now();
|
|
269
|
+
const g = await planComponentGroup({
|
|
270
|
+
group,
|
|
271
|
+
components: byName,
|
|
272
|
+
registry: options.registry,
|
|
273
|
+
env: options.env,
|
|
274
|
+
...(options.vars ? { vars: options.vars } : {}),
|
|
275
|
+
ctxOf: (name) => deployContext(options, name),
|
|
276
|
+
outputs,
|
|
277
|
+
});
|
|
278
|
+
for (const p of g.plans) settle(p.component, start, p);
|
|
279
|
+
const error = g.plans.find((p): p is Extract<WaveComponentPlan, { error: string }> => "error" in p)?.error;
|
|
280
|
+
groups.push({ ...group, digest: g.digest ?? null, ...(error !== undefined ? { error } : {}), ...(g.run ? { run: g.run } : {}) });
|
|
281
|
+
return g.plans;
|
|
282
|
+
}),
|
|
283
|
+
])
|
|
284
|
+
).flat();
|
|
285
|
+
/** What each member of a group planned to expose, for its group-mates' reads. */
|
|
286
|
+
const plannedHere = new Map(results.map((p) => [p.component, "error" in p ? undefined : p.outputs?.known]));
|
|
222
287
|
for (const p of results) {
|
|
223
288
|
const component = byName.get(p.component)!;
|
|
224
|
-
const
|
|
289
|
+
const group = groupOf.get(p.component);
|
|
290
|
+
const mates = group ? new Set(group.components) : undefined;
|
|
291
|
+
const unknown = unknownInputsOf(component, outputs, inChange, mates);
|
|
225
292
|
if (unknown.unknown.length > 0 || unknown.build.length > 0) unknownOf.set(p.component, unknown);
|
|
293
|
+
// A read of a group-mate is known when that mate's plan knows it.
|
|
294
|
+
const readsView = group ? { ...outputs, ...Object.fromEntries(group.components.filter((c) => c !== p.component).map((c) => [c, { ...(plannedHere.get(c) ?? {}) }])) } : outputs;
|
|
295
|
+
const reads = readsOf(component, readsView);
|
|
296
|
+
if (reads.length > 0) readsBy.set(p.component, reads);
|
|
226
297
|
if ("error" in p) {
|
|
227
298
|
failed = true;
|
|
228
299
|
plannedBy.set(p.component, undefined);
|
|
@@ -234,6 +305,7 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
234
305
|
}
|
|
235
306
|
plannedBy.set(p.component, p.outputs);
|
|
236
307
|
plans[p.component] = p.plans;
|
|
308
|
+
Object.assign(shapes, p.shapes);
|
|
237
309
|
for (const part of p.parts) {
|
|
238
310
|
parts.push(part);
|
|
239
311
|
componentOf.set(part.member.member, p.component);
|
|
@@ -246,6 +318,7 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
246
318
|
const members: PrMember[] = doc.members.map((m) => {
|
|
247
319
|
const component = componentOf.get(m.member) ?? m.member;
|
|
248
320
|
const unknown = unknownOf.get(component);
|
|
321
|
+
const reads = readsBy.get(component);
|
|
249
322
|
return {
|
|
250
323
|
member: m.member,
|
|
251
324
|
component,
|
|
@@ -255,17 +328,23 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
255
328
|
...(m.error ? { error: m.error } : {}),
|
|
256
329
|
...(unknown?.unknown.length ? { unknownInputs: unknown.unknown } : {}),
|
|
257
330
|
...(unknown?.build.length ? { buildInputs: unknown.build } : {}),
|
|
331
|
+
...(reads ? { reads } : {}),
|
|
258
332
|
};
|
|
259
333
|
});
|
|
260
334
|
const sorted = (r: Record<string, string[]>) => Object.fromEntries(Object.entries(r).sort(([a], [b]) => a.localeCompare(b)));
|
|
261
335
|
const deployedOutputs = sorted(deployed);
|
|
262
336
|
const plannedOutputs = sorted(planned);
|
|
337
|
+
const at = new Map(options.plan.order.map((name, i) => [name, i]));
|
|
338
|
+
groups.sort((a, b) => (at.get(a.components[0]!) ?? 0) - (at.get(b.components[0]!) ?? 0));
|
|
263
339
|
return {
|
|
264
340
|
doc,
|
|
265
341
|
members,
|
|
266
342
|
plans,
|
|
267
343
|
outputs,
|
|
268
344
|
failed,
|
|
345
|
+
...(groups.length > 0 ? { groups } : {}),
|
|
346
|
+
...(options.plan.fallbacks?.length ? { fallbacks: options.plan.fallbacks } : {}),
|
|
347
|
+
...(Object.keys(shapes).length > 0 ? { shapes } : {}),
|
|
269
348
|
...(Object.keys(deployedOutputs).length > 0 ? { deployedOutputs } : {}),
|
|
270
349
|
...(Object.keys(plannedOutputs).length > 0 ? { plannedOutputs } : {}),
|
|
271
350
|
};
|
|
@@ -277,41 +356,72 @@ export async function planPrSet(options: PrPlanSetOptions): Promise<PrPlanSet> {
|
|
|
277
356
|
* that upstream applies, each sorted. `build` are the published artifact
|
|
278
357
|
* identities a build in the change produces (`WiringReference.publish`):
|
|
279
358
|
* the build's provenance binds those, so they are never approved again.
|
|
280
|
-
* `unknown` are the rest.
|
|
359
|
+
* `unknown` are the rest. A reference to a member of `group`, the
|
|
360
|
+
* component's own group (ws-107), is neither: the group's apply computes it.
|
|
281
361
|
*/
|
|
282
362
|
function unknownInputsOf(
|
|
283
363
|
component: DriverComponent,
|
|
284
364
|
outputs: Record<string, Record<string, unknown>>,
|
|
285
365
|
inChange: ReadonlySet<string>,
|
|
366
|
+
group?: ReadonlySet<string>,
|
|
286
367
|
): { unknown: string[]; build: string[] } {
|
|
287
368
|
const unknown = new Set<string>();
|
|
288
369
|
const build = new Set<string>();
|
|
289
370
|
for (const step of capabilitySteps(component.deploy)) {
|
|
290
371
|
const { kind: _kind, ...rest } = step;
|
|
291
372
|
for (const r of unresolvedWiringReferences(rest, outputs)) {
|
|
373
|
+
if (group?.has(r.source)) continue;
|
|
292
374
|
if (inChange.has(r.source)) (r.publish ? build : unknown).add(r.ref);
|
|
293
375
|
}
|
|
294
376
|
}
|
|
295
377
|
return { unknown: [...unknown].sort(), build: [...build].sort() };
|
|
296
378
|
}
|
|
297
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Every other component's output `component`'s steps read, one entry per
|
|
382
|
+
* upstream, sorted: what the report's `reads` says. A read whose value
|
|
383
|
+
* `outputs` holds is listed as well as one it lacks (`unknown`), so the
|
|
384
|
+
* report names each read between components whether or not the plan knew
|
|
385
|
+
* its value. A component's reads of its own outputs are left out.
|
|
386
|
+
*/
|
|
387
|
+
function readsOf(component: DriverComponent, outputs: Record<string, Record<string, unknown>>): PrMemberRead[] {
|
|
388
|
+
const by = new Map<string, { outputs: Set<string>; unknown: Set<string> }>();
|
|
389
|
+
for (const step of capabilitySteps(component.deploy)) {
|
|
390
|
+
const { kind: _kind, ...rest } = step;
|
|
391
|
+
for (const r of wiringReads(rest, outputs)) {
|
|
392
|
+
if (r.source === component.name) continue;
|
|
393
|
+
const name = r.ref.slice(r.source.length + 1);
|
|
394
|
+
const at = by.get(r.source) ?? { outputs: new Set<string>(), unknown: new Set<string>() };
|
|
395
|
+
by.set(r.source, at);
|
|
396
|
+
at.outputs.add(name);
|
|
397
|
+
if (!r.known) at.unknown.add(name);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return [...by.entries()]
|
|
401
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
402
|
+
.map(([upstream, r]) => ({ upstream, outputs: [...r.outputs].sort(), ...(r.unknown.size > 0 ? { unknown: [...r.unknown].sort() } : {}) }));
|
|
403
|
+
}
|
|
404
|
+
|
|
298
405
|
/**
|
|
299
406
|
* Why `component` cannot apply the plan that read `read` now that `now`
|
|
300
407
|
* stands (#3999): each reference its steps make that resolves to another
|
|
301
408
|
* value, said once. `undefined` when every one resolves as it did. A
|
|
302
409
|
* published artifact's identity from a build in `inChange` never holds a
|
|
303
|
-
* member back: the build's provenance binds it, not this approval.
|
|
410
|
+
* member back: the build's provenance binds it, not this approval. Nor
|
|
411
|
+
* does a reference to a member of `group`, the component's own group
|
|
412
|
+
* (ws-107), whose apply computes it.
|
|
304
413
|
*/
|
|
305
414
|
export function heldReason(
|
|
306
415
|
component: DriverComponent,
|
|
307
416
|
read: Record<string, Record<string, unknown>>,
|
|
308
417
|
now: Record<string, Record<string, unknown>>,
|
|
309
418
|
inChange: ReadonlySet<string> = new Set(),
|
|
419
|
+
group: ReadonlySet<string> = new Set(),
|
|
310
420
|
): string | undefined {
|
|
311
421
|
const refs = new Set<string>();
|
|
312
422
|
for (const step of capabilitySteps(component.deploy)) {
|
|
313
423
|
const { kind: _kind, ...rest } = step;
|
|
314
|
-
for (const r of movedWiringReferences(rest, read, now)) if (!(r.publish && inChange.has(r.source))) refs.add(r.ref);
|
|
424
|
+
for (const r of movedWiringReferences(rest, read, now)) if (!(r.publish && inChange.has(r.source)) && !group.has(r.source)) refs.add(r.ref);
|
|
315
425
|
}
|
|
316
426
|
if (refs.size === 0) return undefined;
|
|
317
427
|
return `its plan read ${[...refs].sort().join(", ")} before ${refs.size === 1 ? "that output" : "those outputs"} applied as another value`;
|
|
@@ -409,13 +519,23 @@ export interface PrApplyOptions {
|
|
|
409
519
|
* The gate is {@link prRoundGate}. Without it, held members are left
|
|
410
520
|
* `waiting` and nothing is planned again.
|
|
411
521
|
*/
|
|
412
|
-
|
|
522
|
+
/**
|
|
523
|
+
* Where the approval of `set` stands (#4419). When it is `approved`, a round
|
|
524
|
+
* whose fresh plan does only what `set` previewed, with only the inputs it
|
|
525
|
+
* marked unknown filled in, applies under that approval without a gate of
|
|
526
|
+
* its own (`coveredRound`), and the round records it (`PrRound.covered`).
|
|
527
|
+
* Without it every round decides its own gate.
|
|
528
|
+
*/
|
|
529
|
+
approval?: PrApproval;
|
|
530
|
+
decideRound?: (round: { round: number; gate: string; set: PrPlanSet; plan: FanOutPlan; notCovered?: string }) => Promise<PrRoundDecision>;
|
|
413
531
|
}
|
|
414
532
|
|
|
415
533
|
/** How a round's gate was decided: whether it applies, and where its approval stands. */
|
|
416
534
|
export interface PrRoundDecision {
|
|
417
535
|
satisfied: boolean;
|
|
418
536
|
approval: PrApproval;
|
|
537
|
+
/** Set when the first approval covered the round (#4419): no gate of its own was decided. */
|
|
538
|
+
covered?: PrRound["covered"];
|
|
419
539
|
/** Why it does not apply, when an approval stands but does not count (`--require-review`). */
|
|
420
540
|
message?: string;
|
|
421
541
|
}
|
|
@@ -430,6 +550,98 @@ export interface PrApplyResult {
|
|
|
430
550
|
rounds?: PrRound[];
|
|
431
551
|
}
|
|
432
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Whether the first approval covers a round's fresh plan (#4419). The
|
|
555
|
+
* approval binds `options.set.doc.digest`; the round's components and
|
|
556
|
+
* members must be ones that plan previewed, and what differs is only what
|
|
557
|
+
* it marked unknown.
|
|
558
|
+
*/
|
|
559
|
+
function roundCover(
|
|
560
|
+
options: PrApplyOptions,
|
|
561
|
+
fresh: PrPlanSet,
|
|
562
|
+
plan: FanOutPlan,
|
|
563
|
+
now: Record<string, Record<string, unknown>>,
|
|
564
|
+
inChange: ReadonlySet<string>,
|
|
565
|
+
byName: ReadonlyMap<string, DriverComponent>,
|
|
566
|
+
): RoundCoverage {
|
|
567
|
+
if (options.approval?.status !== "approved") return { covered: false, reason: "no approval of the first plan stands" };
|
|
568
|
+
// ws-107: a round's group is covered only when it has the approved group's members.
|
|
569
|
+
const moved = groupsMoved(options.set.groups ?? [], fresh.groups ?? [], plan.order);
|
|
570
|
+
if (moved) return { covered: false, reason: moved };
|
|
571
|
+
// The values the round reads of a component it plans again are the ones
|
|
572
|
+
// that plan says it will expose: it has not applied yet, so the outputs
|
|
573
|
+
// standing say nothing of it. An output the first plan knew and the round's
|
|
574
|
+
// plan knows as the same value is not a changed input, whichever of the
|
|
575
|
+
// two applies first.
|
|
576
|
+
const reads: Record<string, Record<string, unknown>> = { ...now };
|
|
577
|
+
for (const name of plan.order) if (fresh.outputs[name]) reads[name] = fresh.outputs[name]!;
|
|
578
|
+
const changedKnown: Record<string, string[]> = {};
|
|
579
|
+
for (const name of plan.order) {
|
|
580
|
+
const component = byName.get(name);
|
|
581
|
+
if (!component) continue;
|
|
582
|
+
const refs = new Set<string>();
|
|
583
|
+
for (const step of capabilitySteps(component.deploy)) {
|
|
584
|
+
const { kind: _kind, ...rest } = step;
|
|
585
|
+
for (const r of movedWiringReferences(rest, options.set.outputs, reads)) if (!(r.publish && inChange.has(r.source))) refs.add(r.ref);
|
|
586
|
+
}
|
|
587
|
+
changedKnown[name] = [...refs].sort();
|
|
588
|
+
}
|
|
589
|
+
// A member whose round plan still reads only outputs of components this
|
|
590
|
+
// round plans and has not applied (a chain of imports, #4466d) cannot apply
|
|
591
|
+
// in this round: it is held again once those apply, and its own round
|
|
592
|
+
// checks its coverage. Only the members this round can apply are checked.
|
|
593
|
+
const inRound = plan.order;
|
|
594
|
+
const fromRound = (ref: string): boolean => inRound.some((c) => ref === c || ref.startsWith(`${c}.`));
|
|
595
|
+
const waits = (m: { unknownInputs?: string[] }): boolean => (m.unknownInputs?.length ?? 0) > 0 && m.unknownInputs!.every(fromRound);
|
|
596
|
+
const approved = { doc: options.set.doc, members: options.set.members, ...(options.set.shapes ? { shapes: options.set.shapes } : {}) };
|
|
597
|
+
return coveredRound({
|
|
598
|
+
approved,
|
|
599
|
+
approvedDigest: options.set.doc.digest,
|
|
600
|
+
fresh: { doc: fresh.doc, members: fresh.members.filter((m) => !waits(m)), ...(fresh.shapes ? { shapes: fresh.shapes } : {}) },
|
|
601
|
+
changedKnown,
|
|
602
|
+
attributes: (member) => memberAttributes(options.set.doc, fresh.doc, member, fresh.shapes?.[member] !== undefined),
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Why the groups `fresh` planned are not the `approved` ones over the
|
|
608
|
+
* components in `order` (ws-107), or `undefined` when each group the round
|
|
609
|
+
* plans has the members the approved group had.
|
|
610
|
+
*/
|
|
611
|
+
function groupsMoved(approved: readonly FanOutGroup[], fresh: readonly FanOutGroup[], order: readonly string[]): string | undefined {
|
|
612
|
+
const same = (a: readonly string[], b: readonly string[]): boolean => a.length === b.length && a.every((c) => b.includes(c));
|
|
613
|
+
const inRound = new Set(order);
|
|
614
|
+
for (const was of approved) {
|
|
615
|
+
const here = was.components.filter((c) => inRound.has(c));
|
|
616
|
+
if (here.length === 0) continue;
|
|
617
|
+
const now = fresh.find((g) => g.components.includes(here[0]!));
|
|
618
|
+
if (!now || !same(now.components, was.components)) {
|
|
619
|
+
return `the approved plan grouped ${was.components.join(", ")}, and the round ${now ? `groups ${now.components.join(", ")}` : `plans ${here.join(", ")} without that group`}`;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
for (const now of fresh) {
|
|
623
|
+
if (!approved.some((was) => same(was.components, now.components))) return `the round groups ${now.components.join(", ")}, which the approved plan did not`;
|
|
624
|
+
}
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* The part of coverage that reads the change set's own attributes, shared by
|
|
630
|
+
* a round, a resume and a wave: a member whose planner describes no changes
|
|
631
|
+
* (and gave no shape) is covered only by the digest it was approved with, and
|
|
632
|
+
* anything else is {@link planCoveredBy}'s.
|
|
633
|
+
*/
|
|
634
|
+
function memberAttributes(approved: ChangeSetDocument, fresh: ChangeSetDocument, member: string, hasShape: boolean): string | undefined {
|
|
635
|
+
if (!hasShape) {
|
|
636
|
+
const now = fresh.members.find((m) => m.member === member);
|
|
637
|
+
const was = approved.members.find((m) => m.member === member);
|
|
638
|
+
if (now && was && now.planner === "chant" && now.lexicon === "chant" && now.status === "planned" && now.planDigest !== was.planDigest) {
|
|
639
|
+
return `${member} now plans to ${now.planDigest}, not the approved ${was.planDigest ?? "(none)"}, and its planner describes no changes to compare`;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return planCoveredBy(approved, fresh, member);
|
|
643
|
+
}
|
|
644
|
+
|
|
433
645
|
/** Two runs of one apply as one: `b` ran after `a`, against the outputs `a` left. */
|
|
434
646
|
function joinRuns(a: FanOutRunResult, b: FanOutRunResult): FanOutRunResult {
|
|
435
647
|
const status: FanOutRunResult["status"] = a.status === "fail" || b.status === "fail" ? "fail" : a.status === "gated" || b.status === "gated" ? "gated" : "ok";
|
|
@@ -444,6 +656,8 @@ function joinRuns(a: FanOutRunResult, b: FanOutRunResult): FanOutRunResult {
|
|
|
444
656
|
componentOutputs: b.componentOutputs,
|
|
445
657
|
...(gate ? { gate } : {}),
|
|
446
658
|
...(b.held ? { held: b.held } : {}),
|
|
659
|
+
...(a.notStarted || b.notStarted ? { notStarted: [...new Set([...(a.notStarted ?? []), ...(b.notStarted ?? [])])].sort() } : {}),
|
|
660
|
+
...(a.groupOutcomes || b.groupOutcomes ? { groupOutcomes: { ...(a.groupOutcomes ?? {}), ...(b.groupOutcomes ?? {}) } } : {}),
|
|
447
661
|
};
|
|
448
662
|
}
|
|
449
663
|
|
|
@@ -467,20 +681,25 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
|
|
|
467
681
|
...Object.fromEntries(Object.entries(options.set.outputs).filter(([name]) => !toApply.has(name))),
|
|
468
682
|
...(options.resumed?.outputs ?? {}),
|
|
469
683
|
};
|
|
470
|
-
const runRound = (plan: FanOutPlan,
|
|
471
|
-
|
|
684
|
+
const runRound = (plan: FanOutPlan, set: Pick<PrPlanSet, "plans" | "groups">, read: Record<string, Record<string, unknown>>, outputs: Record<string, Record<string, unknown>>) => {
|
|
685
|
+
// ws-107: each group runs the plan its group planned, and a read of a group-mate holds nothing back.
|
|
686
|
+
const groupOf = groupsByComponent(plan);
|
|
687
|
+
const groupRuns = Object.fromEntries((set.groups ?? []).filter((g) => g.run).map((g) => [g.components[0]!, g.run!]));
|
|
688
|
+
return runFanOut(plan, options.components, options.registry, {
|
|
472
689
|
env: options.env,
|
|
473
690
|
...(options.vars ? { vars: options.vars } : {}),
|
|
474
|
-
plans,
|
|
691
|
+
plans: set.plans,
|
|
692
|
+
...(Object.keys(groupRuns).length > 0 ? { groupRuns } : {}),
|
|
475
693
|
...(options.targets ? { targets: options.targets } : {}),
|
|
476
694
|
componentOutputs: outputs,
|
|
477
|
-
hold: (name, now) => heldReason(byName.get(name)!, read, now, inChange),
|
|
695
|
+
hold: (name, now) => heldReason(byName.get(name)!, read, now, inChange, new Set(groupOf.get(name)?.components ?? [])),
|
|
478
696
|
...(options.onProgress ? { onProgress: options.onProgress } : {}),
|
|
479
697
|
...(options.onRunEvent ? { onRunEvent: options.onRunEvent } : {}),
|
|
480
698
|
...(options.onComponentSettled ? { onComponentSettled: options.onComponentSettled } : {}),
|
|
481
699
|
});
|
|
700
|
+
};
|
|
482
701
|
|
|
483
|
-
let run = await runRound(options.plan, options.set
|
|
702
|
+
let run = await runRound(options.plan, options.set, options.set.outputs, { ...standing });
|
|
484
703
|
const rounds: PrRound[] = [];
|
|
485
704
|
/** Per component, the round that applied it or holds it, and that round's set, after the first. */
|
|
486
705
|
const roundOf = new Map<string, { round: number; set: PrPlanSet }>();
|
|
@@ -489,7 +708,8 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
|
|
|
489
708
|
const round = rounds.length + 2;
|
|
490
709
|
const gate = prRoundGate(options.gate ?? PR_APPLY_GATE, round);
|
|
491
710
|
const heldNames = new Set(run.held.map((h) => h.component));
|
|
492
|
-
|
|
711
|
+
// A held group is planned again as the same group (ws-107).
|
|
712
|
+
const plan = narrowGroups(remainingFanOut(options.plan, options.components, { completed: options.plan.order.filter((name) => !heldNames.has(name)) }), options.components, options.plan.groups);
|
|
493
713
|
const set = await planPrSet({
|
|
494
714
|
components: options.components,
|
|
495
715
|
plan,
|
|
@@ -500,39 +720,62 @@ export async function applyPrSet(options: PrApplyOptions): Promise<PrApplyResult
|
|
|
500
720
|
...(options.targets ? { targets: options.targets } : {}),
|
|
501
721
|
});
|
|
502
722
|
for (const name of plan.order) roundOf.set(name, { round, set });
|
|
503
|
-
const
|
|
504
|
-
|
|
723
|
+
const cover = roundCover(options, set, plan, run.componentOutputs, inChange, byName);
|
|
724
|
+
const record = (status: PrRound["status"], approval: PrApproval, message?: string, covered?: PrRound["covered"]): void => {
|
|
725
|
+
rounds.push({
|
|
726
|
+
round,
|
|
727
|
+
gate,
|
|
728
|
+
digest: set.doc.digest,
|
|
729
|
+
components: plan.order,
|
|
730
|
+
approval,
|
|
731
|
+
status,
|
|
732
|
+
...(message ? { message } : {}),
|
|
733
|
+
...(covered ? { covered } : {}),
|
|
734
|
+
...(!covered && options.approval?.status === "approved" && cover.covered === false ? { notCovered: cover.reason } : {}),
|
|
735
|
+
changeSet: set.doc,
|
|
736
|
+
});
|
|
505
737
|
};
|
|
506
738
|
if (set.failed) {
|
|
507
739
|
record("plan-failed", { status: "pending" });
|
|
508
740
|
stopped = "plan-failed";
|
|
509
741
|
break;
|
|
510
742
|
}
|
|
511
|
-
const decision =
|
|
743
|
+
const decision: PrRoundDecision =
|
|
744
|
+
cover.covered && options.approval
|
|
745
|
+
? { satisfied: true, approval: options.approval, covered: { digest: cover.digest, filledInputs: cover.filledInputs } }
|
|
746
|
+
: await options.decideRound({ round, gate, set, plan, ...(options.approval?.status === "approved" && !cover.covered ? { notCovered: cover.reason } : {}) });
|
|
512
747
|
if (!decision.satisfied) {
|
|
513
748
|
record("waiting", decision.approval, decision.message);
|
|
514
749
|
stopped = "waiting";
|
|
515
750
|
break;
|
|
516
751
|
}
|
|
517
|
-
const next = await runRound(plan, set
|
|
518
|
-
record(next.status === "fail" ? "failed" : "applied", decision.approval);
|
|
752
|
+
const next = await runRound(plan, set, set.outputs, run.componentOutputs);
|
|
753
|
+
record(next.status === "fail" ? "failed" : "applied", decision.approval, undefined, decision.covered);
|
|
519
754
|
run = joinRuns(run, next);
|
|
520
755
|
}
|
|
521
756
|
|
|
522
757
|
const completed = new Set(run.completed);
|
|
758
|
+
const notStarted = new Set(run.notStarted ?? []);
|
|
523
759
|
const held = new Map((run.held ?? []).map((h) => [h.component, h]));
|
|
524
760
|
const blocked = new Map(run.blocked.map((b) => [b.component, b.blockedBy]));
|
|
525
761
|
const errorOf = (component: string): string | undefined => {
|
|
526
762
|
const result = [...run.results].reverse().find((r) => r.component === component);
|
|
527
763
|
if (result?.status === "gated") return `stopped at its own gate ${result.gate?.gate ?? ""}`.trim();
|
|
528
|
-
return result?.records.find((r) => r.status === "fail")?.error;
|
|
764
|
+
return result?.records.find((r) => r.status === "fail" || (notStarted.has(component) && r.status === "skipped"))?.error;
|
|
529
765
|
};
|
|
530
766
|
const members = options.set.members.map((m): PrMember => {
|
|
531
767
|
if (priorCompleted.has(m.component)) return { ...m, status: "applied" };
|
|
532
768
|
const later = roundOf.get(m.component);
|
|
533
769
|
const fresh = later?.set.members.find((x) => x.member === m.member);
|
|
534
|
-
const
|
|
770
|
+
const planned: PrMember = later && fresh ? { ...m, planDigest: fresh.planDigest, counts: fresh.counts, round: later.round } : m;
|
|
771
|
+
// #4446: how its part of a group's apply ended, as the capability said.
|
|
772
|
+
const groupOutcome = run.groupOutcomes?.[m.component];
|
|
773
|
+
const asPlanned: PrMember = groupOutcome ? { ...planned, groupOutcome } : planned;
|
|
535
774
|
if (completed.has(m.component)) return { ...asPlanned, status: "applied" };
|
|
775
|
+
if (notStarted.has(m.component)) {
|
|
776
|
+
const error = errorOf(m.component);
|
|
777
|
+
return { ...asPlanned, status: "not-started", ...(error ? { error } : {}) };
|
|
778
|
+
}
|
|
536
779
|
const h = held.get(m.component);
|
|
537
780
|
if (h) return { ...asPlanned, status: "waiting", held: h.heldBy ? `${h.heldBy} is held: ${h.reason}` : h.reason };
|
|
538
781
|
if (fresh?.status === "plan-failed") return { ...asPlanned, status: "plan-failed", ...(fresh.error ? { error: fresh.error } : {}) };
|
|
@@ -639,6 +882,12 @@ export interface PrResumeOptions {
|
|
|
639
882
|
record: PrApplyRecord;
|
|
640
883
|
/** Components an earlier attempt applied. */
|
|
641
884
|
completed: string[];
|
|
885
|
+
/**
|
|
886
|
+
* The outputs the components in `completed` left (#4446). A member left
|
|
887
|
+
* of an approved group plans against the outputs its group-mates applied
|
|
888
|
+
* with, which the group's apply computed and the approved plan did not know.
|
|
889
|
+
*/
|
|
890
|
+
applied?: Record<string, Record<string, unknown>>;
|
|
642
891
|
}
|
|
643
892
|
|
|
644
893
|
export type PrResume =
|
|
@@ -672,25 +921,48 @@ export type PrResume =
|
|
|
672
921
|
* is `record.digest`, and the document gives that digest. The digest binds
|
|
673
922
|
* the entries, holes and side effects {@link planCoveredBy} reads (#3555),
|
|
674
923
|
* so a record cannot widen what a re-planned member may do.
|
|
924
|
+
*
|
|
925
|
+
* A group that failed part way (ws-107, "Resume"; #4446) leaves its
|
|
926
|
+
* `partial`, `failed` and `not-started` members; `applied` and `no-changes`
|
|
927
|
+
* ones are in `completed`. What is left of each approved group is grouped
|
|
928
|
+
* again ({@link regroupRemaining}), so it plans as a smaller group, or one
|
|
929
|
+
* by one when the rule or the capability says so. The smaller group's
|
|
930
|
+
* digest differs from the approved one, so coverage rests on entries and
|
|
931
|
+
* shapes, as for any resumed member.
|
|
675
932
|
*/
|
|
676
933
|
export async function resumePrSet(options: PrResumeOptions): Promise<PrResume> {
|
|
677
934
|
const { record } = options;
|
|
678
935
|
if (record.changeSet.digest !== record.digest || !verifyChangeSetDigest(record.changeSet)) {
|
|
679
936
|
return { ok: false, reasons: [`the record's change set is not the one its digest ${record.digest} names`] };
|
|
680
937
|
}
|
|
681
|
-
const plan =
|
|
938
|
+
const plan = await regroupRemaining({
|
|
939
|
+
components: options.components,
|
|
940
|
+
plan: remainingFanOut(options.plan, options.components, { completed: options.completed }),
|
|
941
|
+
registry: options.registry,
|
|
942
|
+
env: options.env,
|
|
943
|
+
...(options.vars ? { vars: options.vars } : {}),
|
|
944
|
+
groups: options.plan.groups,
|
|
945
|
+
...(options.plan.fallbacks ? { fallbacks: options.plan.fallbacks } : {}),
|
|
946
|
+
});
|
|
947
|
+
// The outputs of the applied members of a group with members left: the values the group's apply computed.
|
|
948
|
+
const left = new Set(plan.order);
|
|
949
|
+
const fromGroup = Object.fromEntries(
|
|
950
|
+
(options.plan.groups ?? [])
|
|
951
|
+
.filter((g) => g.components.some((c) => left.has(c)))
|
|
952
|
+
.flatMap((g) => g.components.filter((c) => !left.has(c) && options.applied?.[c] !== undefined))
|
|
953
|
+
.map((c) => [c, options.applied![c]!]),
|
|
954
|
+
);
|
|
682
955
|
const fresh = await planPrSet({
|
|
683
956
|
components: options.components,
|
|
684
957
|
plan,
|
|
685
958
|
registry: options.registry,
|
|
686
959
|
env: options.env,
|
|
687
960
|
...(options.vars ? { vars: options.vars } : {}),
|
|
688
|
-
seededOutputs: { ...(options.seededOutputs ?? {}), ...options.record.planOutputs },
|
|
961
|
+
seededOutputs: { ...(options.seededOutputs ?? {}), ...options.record.planOutputs, ...fromGroup },
|
|
689
962
|
...(options.targets ? { targets: options.targets } : {}),
|
|
690
963
|
});
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
.filter((r): r is string => r !== undefined);
|
|
964
|
+
// #4422: decided as a round decides (`coveredRound`), by the same function a wave uses.
|
|
965
|
+
const reasons = waveNotCovered(options.record.changeSet, fresh.doc, options.record.members);
|
|
694
966
|
if (reasons.length > 0) return { ok: false, reasons, fresh };
|
|
695
967
|
return {
|
|
696
968
|
ok: true,
|
|
@@ -700,7 +972,9 @@ export async function resumePrSet(options: PrResumeOptions): Promise<PrResume> {
|
|
|
700
972
|
doc: options.record.changeSet,
|
|
701
973
|
members: options.record.members.map((m) => ({ ...m, status: "planned" as const })),
|
|
702
974
|
plans: fresh.plans,
|
|
703
|
-
outputs: options.record.planOutputs,
|
|
975
|
+
outputs: { ...options.record.planOutputs, ...fromGroup },
|
|
976
|
+
...(fresh.groups ? { groups: fresh.groups } : {}),
|
|
977
|
+
...(fresh.fallbacks ? { fallbacks: fresh.fallbacks } : {}),
|
|
704
978
|
failed: false,
|
|
705
979
|
},
|
|
706
980
|
};
|
|
@@ -720,7 +994,17 @@ export function prWavePlan(plan: FanOutPlan, components: DriverComponent[], wave
|
|
|
720
994
|
const seeds = [
|
|
721
995
|
...new Set([...plan.seeds, ...order.flatMap((name) => (byName.get(name)?.dependsOn ?? []).filter((dep) => !inWave.has(dep)))]),
|
|
722
996
|
].sort();
|
|
723
|
-
|
|
997
|
+
// ws-107: a group sits in one wave, so what is left of it plans and applies as one there.
|
|
998
|
+
const groups = (plan.groups ?? []).map((g) => ({ ...g, components: g.components.filter((c) => inWave.has(c)) })).filter((g) => g.components.length > 1);
|
|
999
|
+
return {
|
|
1000
|
+
order,
|
|
1001
|
+
waves: order.length > 0 ? [order] : [],
|
|
1002
|
+
skipped: [],
|
|
1003
|
+
seeds,
|
|
1004
|
+
indeterminate: plan.indeterminate,
|
|
1005
|
+
digest: plan.digest,
|
|
1006
|
+
...(groups.length > 0 ? { groups } : {}),
|
|
1007
|
+
};
|
|
724
1008
|
}
|
|
725
1009
|
|
|
726
1010
|
/**
|
|
@@ -731,20 +1015,47 @@ export function prWavePlan(plan: FanOutPlan, components: DriverComponent[], wave
|
|
|
731
1015
|
* returns no change set) is covered only by the digest it was approved
|
|
732
1016
|
* with, since with no entries there is nothing to compare a moved plan by.
|
|
733
1017
|
*/
|
|
734
|
-
export function waveNotCovered(
|
|
1018
|
+
export function waveNotCovered(
|
|
1019
|
+
approved: ChangeSetDocument,
|
|
1020
|
+
fresh: ChangeSetDocument,
|
|
1021
|
+
approvedMembers: readonly PrMember[] = [],
|
|
1022
|
+
shapes: { approved?: Record<string, PlanShape>; fresh?: Record<string, PlanShape> } = {},
|
|
1023
|
+
): string[] {
|
|
735
1024
|
// #3999: a member whose approved plan waited only on an artifact a build in
|
|
736
1025
|
// the change publishes is covered by that build's provenance, as it is
|
|
737
1026
|
// without --wave-gate.
|
|
738
1027
|
const byBuild = new Set(approvedMembers.filter((m) => m.buildInputs?.length && !m.unknownInputs?.length).map((m) => m.member));
|
|
1028
|
+
const prMember = new Map(approvedMembers.map((m) => [m.member, m]));
|
|
1029
|
+
// #4422: the same decision a round makes (`coveredRound`), so a resume, a
|
|
1030
|
+
// wave and a round agree for equivalent inputs. A member the document names
|
|
1031
|
+
// and the caller does not stands for a component of its own name.
|
|
1032
|
+
const approvedSide = {
|
|
1033
|
+
doc: approved,
|
|
1034
|
+
members: approved.members.map((m) => ({
|
|
1035
|
+
member: m.member,
|
|
1036
|
+
component: prMember.get(m.member)?.component ?? m.member,
|
|
1037
|
+
...(prMember.get(m.member)?.unknownInputs?.length ? { unknownInputs: prMember.get(m.member)!.unknownInputs } : {}),
|
|
1038
|
+
})),
|
|
1039
|
+
...(shapes.approved && shapes.fresh ? { shapes: shapes.approved } : {}),
|
|
1040
|
+
};
|
|
739
1041
|
return fresh.members
|
|
740
1042
|
.map((m) => {
|
|
741
1043
|
if (byBuild.has(m.member)) return undefined;
|
|
742
1044
|
const was = approved.members.find((a) => a.member === m.member);
|
|
743
|
-
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1045
|
+
if (!was) return planCoveredBy(approved, fresh, m.member);
|
|
1046
|
+
if (was.planDigest !== null && m.planDigest === was.planDigest) return undefined;
|
|
1047
|
+
const hasShape = shapes.approved !== undefined && shapes.fresh?.[m.member] !== undefined;
|
|
1048
|
+
const cover = coveredRound({
|
|
1049
|
+
approved: approvedSide,
|
|
1050
|
+
approvedDigest: approved.digest,
|
|
1051
|
+
fresh: {
|
|
1052
|
+
doc: fresh,
|
|
1053
|
+
members: [{ member: m.member, component: prMember.get(m.member)?.component ?? m.member }],
|
|
1054
|
+
...(hasShape ? { shapes: shapes.fresh! } : {}),
|
|
1055
|
+
},
|
|
1056
|
+
attributes: (member) => memberAttributes(approved, fresh, member, hasShape),
|
|
1057
|
+
});
|
|
1058
|
+
return cover.covered ? undefined : cover.reason;
|
|
748
1059
|
})
|
|
749
1060
|
.filter((r): r is string => r !== undefined);
|
|
750
1061
|
}
|
|
@@ -778,9 +1089,36 @@ export interface PrReportInput {
|
|
|
778
1089
|
migrations?: PrMigrationFile[];
|
|
779
1090
|
}
|
|
780
1091
|
|
|
1092
|
+
/**
|
|
1093
|
+
* The report's `groups` (#4446): each group the set planned, with the wave
|
|
1094
|
+
* of `waves` its members sit in and the reads of group-mates its members'
|
|
1095
|
+
* change-set parts name, which the group's apply computes.
|
|
1096
|
+
*/
|
|
1097
|
+
function reportGroups(set: PrPlanSet, waves: readonly string[][]): PrGroup[] {
|
|
1098
|
+
const componentOf = new Map(set.members.map((m) => [m.member, m.component]));
|
|
1099
|
+
return (set.groups ?? []).map((g) => {
|
|
1100
|
+
const wave = waves.findIndex((w) => g.components.some((c) => w.includes(c))) + 1;
|
|
1101
|
+
const reads = set.doc.members
|
|
1102
|
+
.filter((m) => g.components.includes(componentOf.get(m.member) ?? m.member))
|
|
1103
|
+
.flatMap((m) => (m.group?.reads ?? []).map((ref) => ({ component: componentOf.get(m.member) ?? m.member, ref })))
|
|
1104
|
+
.sort((a, b) => a.component.localeCompare(b.component) || a.ref.localeCompare(b.ref));
|
|
1105
|
+
return {
|
|
1106
|
+
capability: g.capability,
|
|
1107
|
+
components: [...g.components],
|
|
1108
|
+
wave: wave > 0 ? wave : 1,
|
|
1109
|
+
digest: g.digest,
|
|
1110
|
+
...(g.error !== undefined ? { error: g.error } : {}),
|
|
1111
|
+
...(reads.length > 0 ? { reads } : {}),
|
|
1112
|
+
};
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
|
|
781
1116
|
/** The report a stage writes. */
|
|
782
1117
|
export function prReport(input: PrReportInput): PrReport {
|
|
783
1118
|
const changed = new Set(input.signal.changed);
|
|
1119
|
+
const groups = reportGroups(input.set, input.plan.waves);
|
|
1120
|
+
const groupOf = new Map(groups.flatMap((g, i) => g.components.map((c) => [c, i] as const)));
|
|
1121
|
+
const members = (input.members ?? input.set.members).map((m) => (groupOf.has(m.component) ? { ...m, group: groupOf.get(m.component)! } : m));
|
|
784
1122
|
return {
|
|
785
1123
|
$schema: PR_REPORT_SCHEMA_ID,
|
|
786
1124
|
contract: PR_REPORT_CONTRACT,
|
|
@@ -811,7 +1149,9 @@ export function prReport(input: PrReportInput): PrReport {
|
|
|
811
1149
|
indeterminate: input.plan.indeterminate,
|
|
812
1150
|
waves: input.plan.waves,
|
|
813
1151
|
},
|
|
814
|
-
|
|
1152
|
+
...(groups.length > 0 ? { groups } : {}),
|
|
1153
|
+
...(input.set.fallbacks?.length ? { fallbacks: input.set.fallbacks.map((f) => ({ ...f })) } : {}),
|
|
1154
|
+
members,
|
|
815
1155
|
changeSet: input.set.doc,
|
|
816
1156
|
};
|
|
817
1157
|
}
|