@intentius/chant 0.124.51 → 0.124.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/discover.js +3 -1
- package/dist/behaviour.d.ts +1 -1
- package/dist/behaviour.d.ts.map +1 -1
- package/dist/capabilities/templates.d.ts +65 -0
- package/dist/capabilities/templates.d.ts.map +1 -0
- package/dist/capabilities/templates.js +193 -0
- package/dist/capabilities/templates.json +145 -0
- package/dist/change-set-graph.d.ts +35 -3
- package/dist/change-set-graph.d.ts.map +1 -1
- package/dist/change-set-graph.js +60 -9
- package/dist/change-set.d.ts +65 -7
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +23 -12
- package/dist/ci-identity.d.ts +7 -2
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +4 -1
- package/dist/cli/commands/import-index.d.ts +52 -0
- package/dist/cli/commands/import-index.d.ts.map +1 -0
- package/dist/cli/commands/import-index.js +226 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +4 -2
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/components.js +11 -2
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +16 -3
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +22 -4
- package/dist/cli/handlers/index-cloud.d.ts +14 -0
- package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
- package/dist/cli/handlers/index-cloud.js +22 -0
- package/dist/cli/handlers/index-diff.d.ts +18 -0
- package/dist/cli/handlers/index-diff.d.ts.map +1 -0
- package/dist/cli/handlers/index-diff.js +142 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-drift.js +64 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +188 -28
- package/dist/cli/handlers/misc.d.ts +9 -0
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +77 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +17 -3
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +89 -15
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +28 -4
- package/dist/cli/handlers/scenario.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.js +8 -7
- package/dist/cli/handlers/search.d.ts +34 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +216 -36
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +55 -1
- package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
- package/dist/cli/mcp/lifecycle-tools.js +3 -2
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.js +4 -3
- package/dist/cli/mcp/workspace-tools.js +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +55 -2
- package/dist/cli/registry.d.ts +19 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cloud-lister/aws.d.ts +35 -0
- package/dist/cloud-lister/aws.d.ts.map +1 -0
- package/dist/cloud-lister/aws.js +203 -0
- package/dist/cloud-lister/azure.d.ts +34 -0
- package/dist/cloud-lister/azure.d.ts.map +1 -0
- package/dist/cloud-lister/azure.js +129 -0
- package/dist/cloud-lister/common.d.ts +40 -0
- package/dist/cloud-lister/common.d.ts.map +1 -0
- package/dist/cloud-lister/common.js +83 -0
- package/dist/cloud-lister/credentials.d.ts +50 -0
- package/dist/cloud-lister/credentials.d.ts.map +1 -0
- package/dist/cloud-lister/credentials.js +86 -0
- package/dist/cloud-lister/fakes.d.ts +27 -0
- package/dist/cloud-lister/fakes.d.ts.map +1 -0
- package/dist/cloud-lister/fakes.js +50 -0
- package/dist/cloud-lister/gcp.d.ts +42 -0
- package/dist/cloud-lister/gcp.d.ts.map +1 -0
- package/dist/cloud-lister/gcp.js +143 -0
- package/dist/cloud-lister/guard.d.ts +49 -0
- package/dist/cloud-lister/guard.d.ts.map +1 -0
- package/dist/cloud-lister/guard.js +103 -0
- package/dist/cloud-lister/ids.d.ts +23 -0
- package/dist/cloud-lister/ids.d.ts.map +1 -0
- package/dist/cloud-lister/ids.js +42 -0
- package/dist/cloud-lister/index.d.ts +67 -0
- package/dist/cloud-lister/index.d.ts.map +1 -0
- package/dist/cloud-lister/index.js +102 -0
- package/dist/cloud-lister/kubernetes.d.ts +64 -0
- package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
- package/dist/cloud-lister/kubernetes.js +276 -0
- package/dist/cloud-lister/markers.d.ts +31 -0
- package/dist/cloud-lister/markers.d.ts.map +1 -0
- package/dist/cloud-lister/markers.js +73 -0
- package/dist/cloud-lister/mask.d.ts +31 -0
- package/dist/cloud-lister/mask.d.ts.map +1 -0
- package/dist/cloud-lister/mask.js +74 -0
- package/dist/cloud-lister/sigv4.d.ts +29 -0
- package/dist/cloud-lister/sigv4.d.ts.map +1 -0
- package/dist/cloud-lister/sigv4.js +54 -0
- package/dist/cloud-lister/transport.d.ts +55 -0
- package/dist/cloud-lister/transport.d.ts.map +1 -0
- package/dist/cloud-lister/transport.js +86 -0
- package/dist/cloud-lister/types.d.ts +121 -0
- package/dist/cloud-lister/types.d.ts.map +1 -0
- package/dist/cloud-lister/types.js +24 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +6 -0
- package/dist/components/capability-plugin.d.ts +9 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability.d.ts +224 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +21 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +58 -14
- package/dist/components/covered-round.d.ts +173 -0
- package/dist/components/covered-round.d.ts.map +1 -0
- package/dist/components/covered-round.js +200 -0
- package/dist/components/declared-reads.d.ts +38 -0
- package/dist/components/declared-reads.d.ts.map +1 -0
- package/dist/components/declared-reads.js +131 -0
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/deploy-units.js +4 -0
- package/dist/components/driver.d.ts +42 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +74 -3
- package/dist/components/fan-out-run.d.ts +23 -1
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +82 -14
- package/dist/components/fan-out-support.d.ts +6 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +4 -1
- package/dist/components/fan-out.d.ts +28 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/groups.d.ts +229 -0
- package/dist/components/groups.d.ts.map +1 -0
- package/dist/components/groups.js +760 -0
- package/dist/components/index.d.ts +7 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +5 -0
- package/dist/components/plan-shape-conformance.d.ts +48 -0
- package/dist/components/plan-shape-conformance.d.ts.map +1 -0
- package/dist/components/plan-shape-conformance.js +122 -0
- package/dist/components/pr-run.d.ts +62 -7
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +354 -53
- package/dist/components/promote-rounds.d.ts +9 -0
- package/dist/components/promote-rounds.d.ts.map +1 -1
- package/dist/components/promote-rounds.js +23 -2
- package/dist/components/promote.d.ts +17 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +36 -6
- package/dist/components/release-gate.d.ts +23 -1
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +97 -33
- package/dist/components/run-groups.d.ts +58 -0
- package/dist/components/run-groups.d.ts.map +1 -0
- package/dist/components/run-groups.js +78 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +6 -1
- package/dist/components/verbs/index.d.ts +1 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +1 -0
- package/dist/components/verbs/member-outputs.d.ts +187 -0
- package/dist/components/verbs/member-outputs.d.ts.map +1 -0
- package/dist/components/verbs/member-outputs.js +218 -0
- package/dist/components/wave-plan.d.ts +2 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +4 -1
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -0
- package/dist/deep-observation.d.ts +1 -13
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/deep-observation.js +5 -22
- package/dist/drift-record.d.ts +542 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +886 -0
- package/dist/engine-facts.d.ts +144 -0
- package/dist/engine-facts.d.ts.map +1 -0
- package/dist/engine-facts.js +14 -0
- package/dist/graph-ir.d.ts +153 -2
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +224 -6
- package/dist/graph-refs.d.ts +145 -0
- package/dist/graph-refs.d.ts.map +1 -1
- package/dist/graph-refs.js +305 -0
- package/dist/graph-schema/fixtures/adopt.json +15 -0
- package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/dist/graph-schema/fixtures/index.json +68 -0
- package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/dist/graph-schema/fixtures.d.ts +23 -0
- package/dist/graph-schema/fixtures.d.ts.map +1 -0
- package/dist/graph-schema/fixtures.js +246 -0
- package/dist/graph-schema/generate.d.ts +15 -0
- package/dist/graph-schema/generate.d.ts.map +1 -0
- package/dist/graph-schema/generate.js +267 -0
- package/dist/graph-schema/graph.schema.json +1387 -0
- package/dist/health.schema.json +2 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/instance-nodes.d.ts +159 -0
- package/dist/instance-nodes.d.ts.map +1 -0
- package/dist/instance-nodes.js +231 -0
- package/dist/lexicon.d.ts +72 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/adoption-file.d.ts +56 -0
- package/dist/lifecycle/adoption-file.d.ts.map +1 -0
- package/dist/lifecycle/adoption-file.js +123 -0
- package/dist/lifecycle/cloud-index.d.ts +186 -0
- package/dist/lifecycle/cloud-index.d.ts.map +1 -0
- package/dist/lifecycle/cloud-index.js +618 -0
- package/dist/lifecycle/derive-edges.d.ts +59 -0
- package/dist/lifecycle/derive-edges.d.ts.map +1 -0
- package/dist/lifecycle/derive-edges.js +126 -0
- package/dist/lifecycle/git.d.ts +10 -9
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +7 -1
- package/dist/lifecycle/identity-match.d.ts +133 -0
- package/dist/lifecycle/identity-match.d.ts.map +1 -0
- package/dist/lifecycle/identity-match.js +147 -0
- package/dist/lifecycle/index-diff.d.ts +224 -0
- package/dist/lifecycle/index-diff.d.ts.map +1 -0
- package/dist/lifecycle/index-diff.js +327 -0
- package/dist/lifecycle/index-document.d.ts +130 -0
- package/dist/lifecycle/index-document.d.ts.map +1 -0
- package/dist/lifecycle/index-document.js +189 -0
- package/dist/lifecycle/index-drift.d.ts +121 -0
- package/dist/lifecycle/index-drift.d.ts.map +1 -0
- package/dist/lifecycle/index-drift.js +178 -0
- package/dist/lifecycle/index-import.d.ts +168 -0
- package/dist/lifecycle/index-import.d.ts.map +1 -0
- package/dist/lifecycle/index-import.js +515 -0
- package/dist/lifecycle/index-join.d.ts +129 -0
- package/dist/lifecycle/index-join.d.ts.map +1 -0
- package/dist/lifecycle/index-join.js +248 -0
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/observation-baseline.d.ts +61 -21
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/observation-baseline.js +144 -19
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +6 -1
- package/dist/lifecycle/query.d.ts +45 -4
- package/dist/lifecycle/query.d.ts.map +1 -1
- package/dist/lifecycle/query.js +89 -7
- package/dist/lifecycle/replay.d.ts +10 -3
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/replay.js +10 -6
- package/dist/lifecycle/scenario.d.ts +4 -3
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
- package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-fake.js +21 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-s3.js +254 -0
- package/dist/lifecycle/snapshot-store.d.ts +170 -0
- package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store.js +832 -0
- package/dist/lifecycle/snapshot.d.ts +11 -0
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +18 -5
- package/dist/lifecycle/type-listing.d.ts +148 -0
- package/dist/lifecycle/type-listing.d.ts.map +1 -0
- package/dist/lifecycle/type-listing.js +166 -0
- package/dist/lint/component-checks.d.ts +15 -1
- package/dist/lint/component-checks.d.ts.map +1 -1
- package/dist/lint/component-checks.js +1 -0
- package/dist/lint/presets/relaxed.json +2 -1
- package/dist/lint/presets/strict.json +2 -1
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
- package/dist/lint/rules/comp/index.d.ts +2 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/comp/index.js +4 -1
- package/dist/observation.d.ts +19 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +18 -1
- package/dist/op/activities/activity-contracts.d.ts +1 -1
- package/dist/op/activities/activity-contracts.js +1 -1
- package/dist/op/activities/change-set.d.ts +6 -4
- package/dist/op/activities/change-set.d.ts.map +1 -1
- package/dist/op/activities/change-set.js +4 -2
- package/dist/op/activity-failure.d.ts +27 -0
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-failure.js +31 -0
- package/dist/op/index.d.ts +2 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +5 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -8
- package/dist/pr-loop.d.ts +92 -3
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +62 -3
- package/dist/pulumi-urn.d.ts +101 -0
- package/dist/pulumi-urn.d.ts.map +1 -0
- package/dist/pulumi-urn.js +131 -0
- package/dist/release-floors.d.ts +23 -0
- package/dist/release-floors.d.ts.map +1 -0
- package/dist/release-floors.js +22 -0
- package/dist/sensitive-key.d.ts +23 -0
- package/dist/sensitive-key.d.ts.map +1 -0
- package/dist/sensitive-key.js +31 -0
- package/dist/wave-runtime.mjs +234 -234
- package/dist/workspace/adoption-report.d.ts +53 -0
- package/dist/workspace/adoption-report.d.ts.map +1 -0
- package/dist/workspace/adoption-report.js +76 -0
- package/dist/workspace/capabilities-contract.d.ts +7 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -1
- package/dist/workspace/capabilities-contract.js +2 -1
- package/dist/workspace/capabilities.schema.json +98 -0
- package/dist/workspace/change-set.schema.json +42 -3
- package/dist/workspace/ci-cloud-identity.d.ts +13 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +30 -1
- package/dist/workspace/compose-graph.d.ts +10 -1
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +6 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +6 -1
- package/dist/workspace/drift-cli.d.ts +81 -0
- package/dist/workspace/drift-cli.d.ts.map +1 -0
- package/dist/workspace/drift-cli.js +317 -0
- package/dist/workspace/drift.schema.json +672 -0
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +3 -0
- package/dist/workspace/graph.schema.json +72 -0
- package/dist/workspace/init.d.ts +5 -1
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/init.js +55 -9
- package/dist/workspace/ledger.schema.json +1 -1
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +2 -0
- package/dist/workspace/pr-report.schema.json +76 -3
- package/dist/workspace/promote-waves.d.ts +112 -0
- package/dist/workspace/promote-waves.d.ts.map +1 -0
- package/dist/workspace/promote-waves.js +290 -0
- package/dist/workspace/promote.d.ts +84 -2
- package/dist/workspace/promote.d.ts.map +1 -1
- package/dist/workspace/promote.js +347 -61
- package/dist/workspace/reason-codes.d.ts +12 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +14 -0
- package/dist/workspace/search-contract.d.ts +3 -3
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/search-contract.js +2 -0
- package/dist/workspace/search.schema.json +103 -5
- package/package.json +14 -1
- package/src/__fixtures__/drift-record/README.md +58 -0
- package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
- package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
- package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
- package/src/__fixtures__/drift-record/component-status.json +91 -0
- package/src/__fixtures__/drift-record/deep-diff.json +259 -0
- package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
- package/src/__fixtures__/drift-record/live-diff.json +125 -0
- package/src/audit/discover.e2e.test.ts +14 -0
- package/src/audit/discover.ts +3 -1
- package/src/behaviour.ts +1 -1
- package/src/capabilities/templates.json +145 -0
- package/src/capabilities/templates.test.ts +212 -0
- package/src/capabilities/templates.ts +204 -0
- package/src/change-set-bundle.test.ts +83 -0
- package/src/change-set-graph.ts +80 -9
- package/src/change-set.test.ts +24 -0
- package/src/change-set.ts +77 -14
- package/src/ci-identity-targets.test.ts +23 -0
- package/src/ci-identity.ts +10 -4
- package/src/cli/commands/import-index.test.ts +117 -0
- package/src/cli/commands/import-index.ts +265 -0
- package/src/cli/commands/lint.ts +4 -2
- package/src/cli/handlers/components.test.ts +9 -0
- package/src/cli/handlers/components.ts +12 -2
- package/src/cli/handlers/fan-out.ts +17 -3
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +26 -4
- package/src/cli/handlers/index-cloud.ts +32 -0
- package/src/cli/handlers/index-diff.test.ts +68 -0
- package/src/cli/handlers/index-diff.ts +146 -0
- package/src/cli/handlers/ledger-root-member.test.ts +161 -0
- package/src/cli/handlers/lifecycle-drift.ts +68 -0
- package/src/cli/handlers/lifecycle.test.ts +33 -11
- package/src/cli/handlers/lifecycle.ts +205 -30
- package/src/cli/handlers/misc.ts +73 -0
- package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
- package/src/cli/handlers/pr.ts +16 -3
- package/src/cli/handlers/promote.test.ts +64 -0
- package/src/cli/handlers/promote.ts +81 -6
- package/src/cli/handlers/query.ts +27 -4
- package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
- package/src/cli/handlers/run-groups-cli.test.ts +229 -0
- package/src/cli/handlers/scenario.test.ts +13 -0
- package/src/cli/handlers/scenario.ts +8 -7
- package/src/cli/handlers/search-index.test.ts +255 -0
- package/src/cli/handlers/search-json.test.ts +1 -1
- package/src/cli/handlers/search-ownership.test.ts +194 -0
- package/src/cli/handlers/search.ts +271 -38
- package/src/cli/main.test.ts +7 -0
- package/src/cli/main.ts +56 -1
- package/src/cli/mcp/lifecycle-tools.ts +3 -2
- package/src/cli/mcp/resource-handlers.ts +4 -3
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/cli/parse-args.ts +40 -2
- package/src/cli/registry.ts +19 -1
- package/src/cloud-lister/aws.test.ts +202 -0
- package/src/cloud-lister/aws.ts +226 -0
- package/src/cloud-lister/azure.test.ts +132 -0
- package/src/cloud-lister/azure.ts +142 -0
- package/src/cloud-lister/common.ts +115 -0
- package/src/cloud-lister/credentials.ts +110 -0
- package/src/cloud-lister/fakes.ts +66 -0
- package/src/cloud-lister/gcp.test.ts +121 -0
- package/src/cloud-lister/gcp.ts +159 -0
- package/src/cloud-lister/guard.test.ts +198 -0
- package/src/cloud-lister/guard.ts +111 -0
- package/src/cloud-lister/ids.ts +54 -0
- package/src/cloud-lister/index.ts +134 -0
- package/src/cloud-lister/kubernetes.test.ts +160 -0
- package/src/cloud-lister/kubernetes.ts +284 -0
- package/src/cloud-lister/markers.ts +74 -0
- package/src/cloud-lister/mask.ts +77 -0
- package/src/cloud-lister/shape.test.ts +81 -0
- package/src/cloud-lister/sigv4.ts +70 -0
- package/src/cloud-lister/transport.ts +118 -0
- package/src/cloud-lister/types.ts +135 -0
- package/src/components/capability-plugin-loader.ts +4 -0
- package/src/components/capability-plugin.ts +10 -0
- package/src/components/capability.ts +238 -0
- package/src/components/cli-support.ts +70 -14
- package/src/components/covered-round.test.ts +166 -0
- package/src/components/covered-round.ts +293 -0
- package/src/components/declared-reads.test.ts +103 -0
- package/src/components/declared-reads.ts +134 -0
- package/src/components/deploy-units.ts +4 -0
- package/src/components/driver.ts +101 -4
- package/src/components/fan-out-run.ts +95 -15
- package/src/components/fan-out-support.ts +10 -1
- package/src/components/fan-out.ts +30 -0
- package/src/components/groups.test.ts +320 -0
- package/src/components/groups.ts +880 -0
- package/src/components/index.ts +59 -0
- package/src/components/plan-shape-conformance.ts +130 -0
- package/src/components/pr-env-run-bundle.test.ts +2 -0
- package/src/components/pr-run.test.ts +728 -4
- package/src/components/pr-run.ts +395 -55
- package/src/components/promote-rounds.ts +25 -2
- package/src/components/promote.ts +55 -5
- package/src/components/registry.test.ts +1 -1
- package/src/components/release-gate.ts +115 -30
- package/src/components/release-groups.test.ts +362 -0
- package/src/components/run-groups.ts +113 -0
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +1 -0
- package/src/components/verbs/member-outputs.test.ts +205 -0
- package/src/components/verbs/member-outputs.ts +308 -0
- package/src/components/wave-plan.ts +5 -2
- package/src/config.ts +44 -0
- package/src/deep-observation.ts +5 -23
- package/src/drift-record.test.ts +596 -0
- package/src/drift-record.ts +1206 -0
- package/src/engine-facts.guard.test.ts +111 -0
- package/src/engine-facts.test.ts +178 -0
- package/src/engine-facts.ts +154 -0
- package/src/gated-waves-bundle.test.ts +2 -0
- package/src/graph-ir-live.test.ts +92 -0
- package/src/graph-ir.ts +317 -7
- package/src/graph-refs.ts +416 -0
- package/src/graph-schema/fixtures/adopt.json +15 -0
- package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/src/graph-schema/fixtures/index.json +68 -0
- package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/src/graph-schema/fixtures.ts +267 -0
- package/src/graph-schema/generate.ts +263 -0
- package/src/graph-schema/graph-schema.test.ts +153 -0
- package/src/graph-schema/graph.schema.json +1387 -0
- package/src/health.schema.json +2 -1
- package/src/identity.ts +2 -0
- package/src/index.ts +2 -0
- package/src/instance-nodes.ts +288 -0
- package/src/lexicon.ts +76 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
- package/src/lifecycle/adoption-file.test.ts +100 -0
- package/src/lifecycle/adoption-file.ts +154 -0
- package/src/lifecycle/cloud-index.test.ts +517 -0
- package/src/lifecycle/cloud-index.ts +736 -0
- package/src/lifecycle/derive-edges.test.ts +193 -0
- package/src/lifecycle/derive-edges.ts +182 -0
- package/src/lifecycle/git.ts +10 -4
- package/src/lifecycle/identity-match.test.ts +308 -0
- package/src/lifecycle/identity-match.ts +257 -0
- package/src/lifecycle/index-diff.test.ts +198 -0
- package/src/lifecycle/index-diff.ts +476 -0
- package/src/lifecycle/index-document.test.ts +64 -0
- package/src/lifecycle/index-document.ts +299 -0
- package/src/lifecycle/index-drift.test.ts +191 -0
- package/src/lifecycle/index-drift.ts +304 -0
- package/src/lifecycle/index-import.test.ts +255 -0
- package/src/lifecycle/index-import.ts +634 -0
- package/src/lifecycle/index-join.test.ts +250 -0
- package/src/lifecycle/index-join.ts +304 -0
- package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/member-ledger.test.ts +25 -7
- package/src/lifecycle/observation-baseline.test.ts +72 -1
- package/src/lifecycle/observation-baseline.ts +190 -27
- package/src/lifecycle/observe.ts +6 -1
- package/src/lifecycle/query.test.ts +69 -1
- package/src/lifecycle/query.ts +103 -10
- package/src/lifecycle/replay.test.ts +9 -5
- package/src/lifecycle/replay.ts +9 -5
- package/src/lifecycle/scenario.ts +4 -3
- package/src/lifecycle/snapshot-store-fake.ts +29 -0
- package/src/lifecycle/snapshot-store-s3.ts +316 -0
- package/src/lifecycle/snapshot-store.test.ts +385 -0
- package/src/lifecycle/snapshot-store.ts +981 -0
- package/src/lifecycle/snapshot.test.ts +13 -0
- package/src/lifecycle/snapshot.ts +23 -6
- package/src/lifecycle/type-listing.test.ts +166 -0
- package/src/lifecycle/type-listing.ts +309 -0
- package/src/lint/component-checks.ts +17 -1
- package/src/lint/config.test.ts +3 -0
- package/src/lint/presets/relaxed.json +2 -1
- package/src/lint/presets/strict.json +2 -1
- package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
- package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
- package/src/lint/rules/comp/comp.test.ts +2 -1
- package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
- package/src/lint/rules/comp/comp008.test.ts +77 -0
- package/src/lint/rules/comp/index.ts +4 -1
- package/src/observation-edges.test.ts +33 -0
- package/src/observation.ts +36 -1
- package/src/op/activities/activity-contracts.ts +1 -1
- package/src/op/activities/change-set.ts +8 -5
- package/src/op/activity-failure.ts +36 -0
- package/src/op/index.ts +2 -1
- package/src/op/local-executor.test.ts +80 -0
- package/src/op/local-executor.ts +27 -9
- package/src/pr-loop-bundle.test.ts +5 -2
- package/src/pr-loop.test.ts +57 -1
- package/src/pr-loop.ts +154 -6
- package/src/pulumi-urn.ts +151 -0
- package/src/release-floors.ts +22 -0
- package/src/sensitive-key.ts +34 -0
- package/src/workspace/adoption-report.test.ts +61 -0
- package/src/workspace/adoption-report.ts +103 -0
- package/src/workspace/capabilities-contract.test.ts +4 -0
- package/src/workspace/capabilities-contract.ts +9 -1
- package/src/workspace/capabilities.schema.json +98 -0
- package/src/workspace/change-set.schema.json +42 -3
- package/src/workspace/ci-cloud-identity.ts +31 -1
- package/src/workspace/compose-graph.test.ts +12 -0
- package/src/workspace/compose-graph.ts +12 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.ts +6 -1
- package/src/workspace/drift-cli.test.ts +65 -0
- package/src/workspace/drift-cli.ts +365 -0
- package/src/workspace/drift.schema.json +672 -0
- package/src/workspace/graph-cli.ts +5 -0
- package/src/workspace/graph.schema.json +72 -0
- package/src/workspace/init.test.ts +38 -1
- package/src/workspace/init.ts +50 -8
- package/src/workspace/intent-gaps.test.ts +18 -2
- package/src/workspace/ledger.schema.json +1 -1
- package/src/workspace/nested-graph.ts +1 -0
- package/src/workspace/pr-report.schema.json +76 -3
- package/src/workspace/promote-waves.test.ts +296 -0
- package/src/workspace/promote-waves.ts +340 -0
- package/src/workspace/promote.test.ts +158 -4
- package/src/workspace/promote.ts +398 -57
- package/src/workspace/reason-codes.test.ts +4 -1
- package/src/workspace/reason-codes.ts +14 -0
- package/src/workspace/search-contract.ts +2 -0
- package/src/workspace/search.schema.json +103 -5
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `member-outputs` (#4455): a chant member's parameter planned against
|
|
3
|
+
* another workspace member's output, read through that member's engine.
|
|
4
|
+
*
|
|
5
|
+
* The estate is the three-cloud demo's: `dns` reads `azureEndpoint`, which
|
|
6
|
+
* the `azure` member publishes. `azure` is a choudoufu member, read through
|
|
7
|
+
* a fake engine source standing in for the terraform lexicon's
|
|
8
|
+
* `choudoufuOutputSource`; `records` is dns's deploy, a fake step that
|
|
9
|
+
* plans its parameters the way `cfn-deploy` does (a hole when an input is
|
|
10
|
+
* not known yet).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, expect, test } from "vitest";
|
|
14
|
+
import { CapabilityRegistry, type DeployContext } from "../capability";
|
|
15
|
+
import { registerCapabilityPlugins } from "../capability-plugin-loader";
|
|
16
|
+
import type { CapabilityPlugin } from "../capability-plugin";
|
|
17
|
+
import { starterCapabilityPlugin } from "../starter-plugin";
|
|
18
|
+
import { planFanOut } from "../fan-out";
|
|
19
|
+
import { applyPrSet, approvalOf, decidePrGate, planPrSet, readOnlyLedger, type PrPlanSet } from "../pr-run";
|
|
20
|
+
import { memoryGateLedgerPort } from "../../op/gate";
|
|
21
|
+
import { computePlanDigest } from "../../lifecycle/plan-digest";
|
|
22
|
+
import { deployUnits } from "../deploy-units";
|
|
23
|
+
import type { DriverComponent } from "../driver";
|
|
24
|
+
import {
|
|
25
|
+
EngineOutputSourceMissingError,
|
|
26
|
+
MEMBER_OUTPUTS_KIND,
|
|
27
|
+
memberOutputsCapability,
|
|
28
|
+
memberOutputsComponent,
|
|
29
|
+
memberOutputsMemberName,
|
|
30
|
+
MemberOutputsPendingError,
|
|
31
|
+
type EngineMemberRef,
|
|
32
|
+
type EngineOutputSource,
|
|
33
|
+
type EnginePlannedOutputs,
|
|
34
|
+
} from "./member-outputs";
|
|
35
|
+
|
|
36
|
+
interface RecordsInput {
|
|
37
|
+
inputs: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** dns's two components: the azure member's outputs, and the records reading `azureEndpoint`. */
|
|
41
|
+
const ESTATE: DriverComponent[] = [
|
|
42
|
+
memberOutputsComponent("azure") as unknown as DriverComponent,
|
|
43
|
+
{
|
|
44
|
+
name: "records",
|
|
45
|
+
dependsOn: ["azure"],
|
|
46
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "fake-records", inputs: { azureEndpoint: { stackOutput: { stack: "azure", name: "azureEndpoint" } } } }] }],
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const WORKSPACE = "/ws";
|
|
51
|
+
const lookup = async (_cwd: string, member: string): Promise<EngineMemberRef> => {
|
|
52
|
+
if (member !== "azure") throw new Error(`no member ${member}`);
|
|
53
|
+
return { member, kind: "choudoufu", dir: `${WORKSPACE}/azure`, workspaceRoot: WORKSPACE };
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function world() {
|
|
57
|
+
const w = {
|
|
58
|
+
/** What the azure estate's outputs are now. */
|
|
59
|
+
applied: { azureEndpoint: "web.old.azurecontainerapps.io" } as Record<string, unknown>,
|
|
60
|
+
/** What azure's plan says. */
|
|
61
|
+
plan: { known: { azureEndpoint: "web.old.azurecontainerapps.io" }, unknown: [] as string[], planDigest: "sha256:azure-plan", changes: false } as EnginePlannedOutputs,
|
|
62
|
+
/** What each plan of records saw. */
|
|
63
|
+
recordsPlanned: [] as Array<{ inputs: Record<string, unknown>; unresolved?: string[] }>,
|
|
64
|
+
recordsRan: [] as Array<Record<string, unknown>>,
|
|
65
|
+
refs: [] as EngineMemberRef[],
|
|
66
|
+
};
|
|
67
|
+
const source: EngineOutputSource = {
|
|
68
|
+
kinds: ["choudoufu"],
|
|
69
|
+
async applied(_ctx, ref) {
|
|
70
|
+
w.refs.push(ref);
|
|
71
|
+
return { ...w.applied };
|
|
72
|
+
},
|
|
73
|
+
async planned(_ctx, ref) {
|
|
74
|
+
w.refs.push(ref);
|
|
75
|
+
return { ...w.plan, known: { ...w.plan.known }, unknown: [...w.plan.unknown] };
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const plugin: CapabilityPlugin = {
|
|
79
|
+
name: "fake-dns",
|
|
80
|
+
version: "0.0.0",
|
|
81
|
+
capabilities: () =>
|
|
82
|
+
[
|
|
83
|
+
{
|
|
84
|
+
kind: "fake-records",
|
|
85
|
+
async plan(ctx: DeployContext, input: RecordsInput) {
|
|
86
|
+
w.recordsPlanned.push({ inputs: { ...input.inputs }, ...(ctx.unresolvedInputs ? { unresolved: [...ctx.unresolvedInputs] } : {}) });
|
|
87
|
+
const planDigest = computePlanDigest("fake-records", { inputs: input.inputs, unresolved: ctx.unresolvedInputs ?? [] });
|
|
88
|
+
const holes = ctx.unresolvedInputs?.length ? [{ address: "records", reason: `inputs from ${ctx.unresolvedInputs.join(", ")}; re-checked on apply` }] : [];
|
|
89
|
+
return {
|
|
90
|
+
member: "records",
|
|
91
|
+
planDigest,
|
|
92
|
+
artifact: { planDigest },
|
|
93
|
+
changeSet: { member: { member: "records", lexicon: "aws", planner: "chant", status: "planned", planDigest, holes }, entries: [] },
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
async run(_ctx: DeployContext, input: RecordsInput) {
|
|
97
|
+
w.recordsRan.push({ ...input.inputs });
|
|
98
|
+
return {};
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
] as never,
|
|
102
|
+
engineOutputSources: () => [source],
|
|
103
|
+
};
|
|
104
|
+
// The registry the CLI builds: the starter set, whose member-outputs gets the plugins' sources bound.
|
|
105
|
+
const starter = { ...starterCapabilityPlugin, capabilities: () => starterCapabilityPlugin.capabilities().filter((c) => c.kind !== MEMBER_OUTPUTS_KIND).concat([memberOutputsCapability({ lookup }) as never]) };
|
|
106
|
+
const registry = registerCapabilityPlugins(new CapabilityRegistry(), [starter as CapabilityPlugin, plugin]);
|
|
107
|
+
return { registry, state: w };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const NOW = "2026-10-25T00:00:00Z";
|
|
111
|
+
|
|
112
|
+
describe("member-outputs", () => {
|
|
113
|
+
test("is in the starter set, and a registry built without a source for the member's kind refuses by name", async () => {
|
|
114
|
+
expect(starterCapabilityPlugin.capabilities().map((c) => c.kind)).toContain(MEMBER_OUTPUTS_KIND);
|
|
115
|
+
const bare = memberOutputsCapability({ lookup });
|
|
116
|
+
await expect(bare.outputs!({ env: "prod", component: "azure" }, { member: "azure" })).rejects.toBeInstanceOf(EngineOutputSourceMissingError);
|
|
117
|
+
await expect(bare.outputs!({ env: "prod", component: "azure" }, { member: "azure" })).rejects.toThrow(/member "azure" is kind choudoufu.*Add "terraform"/);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("the component is a deploy unit named after the member", () => {
|
|
121
|
+
expect(deployUnits(memberOutputsComponent("azure").deploy)).toEqual([{ unit: "azure", lexicon: "workspace" }]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("an applied output feeds dns's parameter when the change does not touch the member", async () => {
|
|
125
|
+
const w = world();
|
|
126
|
+
const plan = planFanOut({ components: ESTATE, changed: ["records"] });
|
|
127
|
+
const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
|
|
128
|
+
expect(set.failed).toBe(false);
|
|
129
|
+
expect(w.state.recordsPlanned).toEqual([{ inputs: { azureEndpoint: "web.old.azurecontainerapps.io" } }]);
|
|
130
|
+
expect(set.deployedOutputs).toEqual({ azure: ["azureEndpoint"] });
|
|
131
|
+
expect(w.state.refs[0]).toEqual({ member: "azure", kind: "choudoufu", dir: "/ws/azure", workspaceRoot: "/ws" });
|
|
132
|
+
const records = set.members.find((m) => m.member === "records")!;
|
|
133
|
+
expect(records.unknownInputs).toBeUndefined();
|
|
134
|
+
expect(records.reads).toEqual([{ upstream: "azure", outputs: ["azureEndpoint"] }]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("a planned output the member's plan knows feeds dns's parameter when the change touches the member", async () => {
|
|
138
|
+
const w = world();
|
|
139
|
+
w.state.plan = { known: { azureEndpoint: "web.new.azurecontainerapps.io" }, unknown: [], planDigest: "sha256:azure-v2", changes: true };
|
|
140
|
+
const plan = planFanOut({ components: ESTATE, changed: ["azure"] });
|
|
141
|
+
const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
|
|
142
|
+
expect(w.state.recordsPlanned).toEqual([{ inputs: { azureEndpoint: "web.new.azurecontainerapps.io" } }]);
|
|
143
|
+
expect(set.plannedOutputs).toEqual({ azure: ["azureEndpoint"] });
|
|
144
|
+
const azure = set.doc.members.find((m) => m.member === memberOutputsMemberName("azure"))!;
|
|
145
|
+
expect(azure.holes).toEqual([]);
|
|
146
|
+
// The approval binds the member's plan: another plan of azure moves the set's digest.
|
|
147
|
+
const moved = world();
|
|
148
|
+
moved.state.plan = { ...w.state.plan, planDigest: "sha256:azure-v3" };
|
|
149
|
+
const again = await planPrSet({ components: ESTATE, plan, registry: moved.registry, env: "prod" });
|
|
150
|
+
expect(again.doc.digest).not.toBe(set.doc.digest);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("an unknown upstream output holds dns back, naming the member and the output", async () => {
|
|
154
|
+
const w = world();
|
|
155
|
+
w.state.plan = { known: {}, unknown: ["azureEndpoint"], planDigest: "sha256:azure-new-app", changes: true };
|
|
156
|
+
const plan = planFanOut({ components: ESTATE, changed: ["azure"] });
|
|
157
|
+
const set = await planPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod" });
|
|
158
|
+
expect(set.failed).toBe(false);
|
|
159
|
+
|
|
160
|
+
// records was planned without the value, told whose output it waits on.
|
|
161
|
+
expect(w.state.recordsPlanned).toEqual([{ inputs: {}, unresolved: ["azure"] }]);
|
|
162
|
+
const records = set.members.find((m) => m.member === "records")!;
|
|
163
|
+
expect(records.unknownInputs).toEqual(["azure.azureEndpoint"]);
|
|
164
|
+
expect(records.reads).toEqual([{ upstream: "azure", outputs: ["azureEndpoint"], unknown: ["azureEndpoint"] }]);
|
|
165
|
+
expect(set.doc.members.find((m) => m.member === "records")!.holes).toEqual([{ address: "records", reason: "inputs from azure; re-checked on apply" }]);
|
|
166
|
+
// The member's own part names the output.
|
|
167
|
+
expect(set.doc.members.find((m) => m.member === memberOutputsMemberName("azure"))!.holes).toEqual([
|
|
168
|
+
{ address: "azure.azureEndpoint", type: "output", reason: "azure.azureEndpoint is known only after azure applies; what reads it is planned again then" },
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
// On merge, before azure's own pipeline applied: the read refuses, so records is blocked behind it and nothing reads a stale value.
|
|
172
|
+
const early = await applyPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", set });
|
|
173
|
+
expect(early.members.find((m) => m.member === "records")).toMatchObject({ status: "blocked", blockedBy: "azure" });
|
|
174
|
+
expect(w.state.recordsRan).toEqual([]);
|
|
175
|
+
|
|
176
|
+
// Once azure applied: records is held back with the reason, and planned again against the value azure applied as.
|
|
177
|
+
w.state.applied = { azureEndpoint: "web.new.azurecontainerapps.io" };
|
|
178
|
+
w.state.plan = { known: { azureEndpoint: "web.new.azurecontainerapps.io" }, unknown: [], planDigest: "sha256:azure-applied", changes: false };
|
|
179
|
+
const decideNothing = async ({ gate, set: round }: { gate: string; set: PrPlanSet }) => {
|
|
180
|
+
const check = await decidePrGate(readOnlyLedger(memoryGateLedgerPort()), { op: "pr-12", gate, digest: round.doc.digest, now: NOW });
|
|
181
|
+
return { satisfied: check.satisfied, approval: approvalOf(check) };
|
|
182
|
+
};
|
|
183
|
+
const held = await applyPrSet({ components: ESTATE, plan, registry: w.registry, env: "prod", set, decideRound: decideNothing });
|
|
184
|
+
expect(held.status).toBe("waiting");
|
|
185
|
+
expect(held.members.find((m) => m.member === "records")).toMatchObject({
|
|
186
|
+
status: "waiting",
|
|
187
|
+
round: 2,
|
|
188
|
+
held: "its plan read azure.azureEndpoint before that output applied as another value",
|
|
189
|
+
});
|
|
190
|
+
expect(w.state.recordsPlanned.at(-1)).toEqual({ inputs: { azureEndpoint: "web.new.azurecontainerapps.io" } });
|
|
191
|
+
expect(w.state.recordsRan).toEqual([]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("run reads and never applies: a member with changes left refuses, one without answers its applied outputs", async () => {
|
|
195
|
+
const w = world();
|
|
196
|
+
const capability = w.registry.resolve(MEMBER_OUTPUTS_KIND);
|
|
197
|
+
w.state.plan = { ...w.state.plan, changes: true };
|
|
198
|
+
await expect(capability.run({ env: "prod", component: "azure" }, { member: "azure" } as never)).rejects.toBeInstanceOf(MemberOutputsPendingError);
|
|
199
|
+
w.state.plan = { ...w.state.plan, changes: false };
|
|
200
|
+
await expect(capability.run({ env: "prod", component: "azure" }, { member: "azure" } as never)).resolves.toEqual({
|
|
201
|
+
member: "azure",
|
|
202
|
+
outputs: { azureEndpoint: "web.old.azurecontainerapps.io" },
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `member-outputs`: another workspace member's outputs, read through the
|
|
3
|
+
* engine that member runs on, so a chant member's parameters can be planned
|
|
4
|
+
* against them (#4455, design record choudoufu#2258 lane T6).
|
|
5
|
+
*
|
|
6
|
+
* In `behold/example-three-cloud`, `dns` (a chant member) reads each cloud's
|
|
7
|
+
* endpoint as a parameter of the same name as the output the cloud's member
|
|
8
|
+
* publishes. When the Azure site is a choudoufu estate, `azureEndpoint` is a
|
|
9
|
+
* Terraform root output, and when the AWS site is a Pulumi stack it is a
|
|
10
|
+
* stack output. Neither is a chant component, so there is nothing for a
|
|
11
|
+
* `stackOutput()` reference to read. This verb is that something: a
|
|
12
|
+
* component in the consumer's project whose one step names the upstream
|
|
13
|
+
* member, and whose capability answers the two questions the components
|
|
14
|
+
* machinery already asks of an upstream:
|
|
15
|
+
*
|
|
16
|
+
* - `outputs` (applied): what the member exposes now. A pull request that
|
|
17
|
+
* does not touch the member plans the consumer against these
|
|
18
|
+
* (`deployedOutputs`, #4104).
|
|
19
|
+
* - `plan` (planned): what the member will expose once its own plan applies,
|
|
20
|
+
* with the outputs known only after apply named (`PlannedOutputs`, #3999).
|
|
21
|
+
* A pull request that touches the member plans the consumer against the
|
|
22
|
+
* known ones. A reference to an unknown one is unresolved, so the
|
|
23
|
+
* consumer's step is planned with `DeployContext.unresolvedInputs` naming
|
|
24
|
+
* the member, the pull request's report lists `<member>.<output>` under the
|
|
25
|
+
* consumer's `unknownInputs`, and the apply holds the consumer back until
|
|
26
|
+
* the member applied, then plans it again (`pr-run.ts`, "Planning again
|
|
27
|
+
* once an upstream applied"). That is the hold chant already puts on a
|
|
28
|
+
* root waiting on another root's unknown output; nothing here adds a
|
|
29
|
+
* second one.
|
|
30
|
+
*
|
|
31
|
+
* The consumer writes it as two components:
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* export const azure = memberOutputsComponent("azure");
|
|
35
|
+
* export const records: Component = {
|
|
36
|
+
* name: "records",
|
|
37
|
+
* dependsOn: ["azure"],
|
|
38
|
+
* deploy: [phase("Apply", [{ kind: "cfn-deploy", template: "dist/dns.json",
|
|
39
|
+
* inputs: { azureEndpoint: stackOutput("azure", "azureEndpoint") } }])],
|
|
40
|
+
* };
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ## Engines: the seam
|
|
44
|
+
*
|
|
45
|
+
* The member is looked up in the workspace declaration (`chant.workspace.json`)
|
|
46
|
+
* by name, and its `kind` picks the {@link EngineOutputSource} that reads it.
|
|
47
|
+
* Sources are not capabilities: each lexicon that drives an engine
|
|
48
|
+
* contributes one through `CapabilityPlugin.engineOutputSources()`, the way
|
|
49
|
+
* the cloud lexicons contribute registry logins to `publish-image`, and the
|
|
50
|
+
* registry binds them into this one verb when it is built
|
|
51
|
+
* (`registerCapabilityPlugins`). So two engines never register the same
|
|
52
|
+
* kind, and a consumer's step does not change when the member moves between
|
|
53
|
+
* engines.
|
|
54
|
+
*
|
|
55
|
+
* - `choudoufu` (the terraform lexicon, `choudoufuOutputSource`): applied
|
|
56
|
+
* values from `choudoufu output -json` in the member's directory, which in
|
|
57
|
+
* live mode reads the estate's record store; planned values from
|
|
58
|
+
* `live-plan-set -json` over the member's root, its plan's
|
|
59
|
+
* `planned_values.outputs` with every output whose `output_changes` entry
|
|
60
|
+
* marks any part `after_unknown` named unknown (`terraformPlannedOutputs`).
|
|
61
|
+
* - `pulumi` (the pulumi lexicon, `pulumiOutputSource`): applied values
|
|
62
|
+
* from `pulumi stack output --json --show-secrets=false`, or from the
|
|
63
|
+
* member's checked-in `stack-export.json` when chant must not run pulumi
|
|
64
|
+
* (`CHANT_PULUMI_APPLIED_OUTPUTS=export`); planned values from `pulumi
|
|
65
|
+
* preview --json`, the stack step's outputs, where an output holding
|
|
66
|
+
* Pulumi's unknown sentinel is unknown. Secret outputs are left out of
|
|
67
|
+
* both. A member on plunge runs the same commands under `plunge`.
|
|
68
|
+
* Nothing in this module or in the consumer changes for it.
|
|
69
|
+
*
|
|
70
|
+
* ## Run reads, it never applies
|
|
71
|
+
*
|
|
72
|
+
* The member applies through its own pipeline. `run` checks that the
|
|
73
|
+
* member's plan has nothing left to apply (`changes` false) and answers its
|
|
74
|
+
* applied outputs; a member with changes still to apply refuses the run
|
|
75
|
+
* with {@link MemberOutputsPendingError}, so the consumer is blocked behind
|
|
76
|
+
* it and never applies against values from before the member's change. A
|
|
77
|
+
* workspace orders the member's own run before the consumer's (members that
|
|
78
|
+
* link to a producer run after it, `chant workspace run`).
|
|
79
|
+
*
|
|
80
|
+
* ## Which changes reach the member
|
|
81
|
+
*
|
|
82
|
+
* The component is a deploy unit named after the member (`DEPLOY_UNIT_RULES`),
|
|
83
|
+
* so a change signal that names the member as a changed unit puts it in the
|
|
84
|
+
* change. The terraform lexicon names it when the consumer's
|
|
85
|
+
* `terraform.roots` has an entry of that name whose files changed;
|
|
86
|
+
* `--from-affected` can name it too.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
import type { Capability, CapabilityPlan, DeployContext, PlannedOutputs } from "../capability";
|
|
90
|
+
import type { ChangeSetHole, ChangeSetPart } from "../../change-set";
|
|
91
|
+
import { computePlanDigest } from "../../lifecycle/plan-digest";
|
|
92
|
+
import type { Component } from "../component";
|
|
93
|
+
|
|
94
|
+
/** The step kind. */
|
|
95
|
+
export const MEMBER_OUTPUTS_KIND = "member-outputs";
|
|
96
|
+
|
|
97
|
+
/** The `member-outputs` step's input. */
|
|
98
|
+
export interface MemberOutputsInput {
|
|
99
|
+
/** The upstream member's name in the workspace declaration. Also the component's name, by convention. */
|
|
100
|
+
member: string;
|
|
101
|
+
/**
|
|
102
|
+
* Where the declaration search starts. Default: the current directory,
|
|
103
|
+
* which is the consumer's project root under `chant run`.
|
|
104
|
+
*/
|
|
105
|
+
cwd?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** What `run` returns: the member's applied outputs, under `outputs`, where the driver collects a stack's outputs from. */
|
|
109
|
+
export interface MemberOutputsOutput {
|
|
110
|
+
member: string;
|
|
111
|
+
outputs: Record<string, unknown>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The upstream member a source reads, as the workspace declares it. */
|
|
115
|
+
export interface EngineMemberRef {
|
|
116
|
+
/** The member's name. */
|
|
117
|
+
member: string;
|
|
118
|
+
/** Its declared kind, which picked the source: `choudoufu`, `pulumi`. */
|
|
119
|
+
kind: string;
|
|
120
|
+
/** Its directory, absolute. */
|
|
121
|
+
dir: string;
|
|
122
|
+
/** The workspace root, absolute. A source that must run from above the member's directory runs from here. */
|
|
123
|
+
workspaceRoot: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** What a source's plan of the member says about its outputs. */
|
|
127
|
+
export interface EnginePlannedOutputs extends PlannedOutputs {
|
|
128
|
+
/**
|
|
129
|
+
* A digest of the member's plan, the engine's own or chant's over it. The
|
|
130
|
+
* consumer's approval binds it, so a plan of the member that moves after
|
|
131
|
+
* review moves the consumer's digest too.
|
|
132
|
+
*/
|
|
133
|
+
planDigest: string;
|
|
134
|
+
/** Whether the member's plan has anything to apply. `run` refuses while it does. */
|
|
135
|
+
changes: boolean;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Reads one engine's member outputs (#4455). One per engine, contributed by
|
|
140
|
+
* the lexicon that drives it through `CapabilityPlugin.engineOutputSources()`.
|
|
141
|
+
*/
|
|
142
|
+
export interface EngineOutputSource {
|
|
143
|
+
/** The member kinds this source reads, as declared in `chant.workspace.json`. */
|
|
144
|
+
readonly kinds: readonly string[];
|
|
145
|
+
/** The member's outputs as applied now, by name. */
|
|
146
|
+
applied(ctx: DeployContext, ref: EngineMemberRef): Promise<Record<string, unknown>>;
|
|
147
|
+
/** The member's outputs once its current plan applies, the ones known only after apply named. */
|
|
148
|
+
planned(ctx: DeployContext, ref: EngineMemberRef): Promise<EnginePlannedOutputs>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Finds a member in the workspace above `cwd`: its kind and directory. */
|
|
152
|
+
export type MemberLookup = (cwd: string, member: string) => Promise<EngineMemberRef>;
|
|
153
|
+
|
|
154
|
+
/** No source for the member's kind is loaded. */
|
|
155
|
+
export class EngineOutputSourceMissingError extends Error {
|
|
156
|
+
constructor(
|
|
157
|
+
readonly member: string,
|
|
158
|
+
readonly kind: string,
|
|
159
|
+
readonly available: readonly string[],
|
|
160
|
+
) {
|
|
161
|
+
super(
|
|
162
|
+
`${MEMBER_OUTPUTS_KIND}: member "${member}" is kind ${kind}, and no loaded lexicon reads that engine's outputs` +
|
|
163
|
+
(available.length > 0 ? ` (loaded: ${available.join(", ")})` : "") +
|
|
164
|
+
`. ${kind === "choudoufu" ? 'Add "terraform" to the project\'s lexicons.' : kind === "pulumi" ? 'Add "pulumi" to the project\'s lexicons.' : "Load the lexicon that drives it."}`,
|
|
165
|
+
);
|
|
166
|
+
this.name = "EngineOutputSourceMissingError";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The member's plan has changes its own pipeline has not applied. Nothing was read. */
|
|
171
|
+
export class MemberOutputsPendingError extends Error {
|
|
172
|
+
constructor(readonly member: string) {
|
|
173
|
+
super(
|
|
174
|
+
`${MEMBER_OUTPUTS_KIND}: member "${member}" has changes its own pipeline has not applied yet, ` +
|
|
175
|
+
`so its outputs are still the ones from before them. Apply ${member} first; whatever reads its outputs runs after it.`,
|
|
176
|
+
);
|
|
177
|
+
this.name = "MemberOutputsPendingError";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The member a {@link MemberOutputsInput} reads plans as, in the consumer's change set. */
|
|
182
|
+
export const memberOutputsMemberName = (member: string): string => `${member}#outputs`;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The member's name in the declaration above `cwd`, with its kind and
|
|
186
|
+
* directory. Reads the working tree. Imported lazily: the verb set is
|
|
187
|
+
* loaded where the workspace reader's dependencies may not be.
|
|
188
|
+
*/
|
|
189
|
+
export const workspaceMemberLookup: MemberLookup = async (cwd, member) => {
|
|
190
|
+
const { locateWorkspace } = await import("../../workspace/which-chant");
|
|
191
|
+
const { readDeclaration } = await import("../../workspace/declaration");
|
|
192
|
+
const { join } = await import("node:path");
|
|
193
|
+
const located = locateWorkspace(cwd);
|
|
194
|
+
const declaration = readDeclaration(located.tree);
|
|
195
|
+
const found = declaration.members.find((m) => m.name === member);
|
|
196
|
+
if (!found) {
|
|
197
|
+
const names = declaration.members.map((m) => m.name).sort();
|
|
198
|
+
throw new Error(`${MEMBER_OUTPUTS_KIND}: the workspace at ${located.rootOnDisk} declares no member "${member}" (members: ${names.join(", ")})`);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
member: found.name,
|
|
202
|
+
kind: found.kind,
|
|
203
|
+
dir: found.dir === "." ? located.rootOnDisk : join(located.rootOnDisk, ...found.dir.split("/")),
|
|
204
|
+
workspaceRoot: located.rootOnDisk,
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
function requireMember(input: MemberOutputsInput): void {
|
|
209
|
+
if (typeof input?.member !== "string" || input.member === "") {
|
|
210
|
+
throw new Error(`${MEMBER_OUTPUTS_KIND}: "member" is required and names a member of the workspace`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Implemented by a capability that wants the plugins' engine output sources injected when a registry is built. */
|
|
215
|
+
export interface EngineOutputSourceConsumer {
|
|
216
|
+
withEngineOutputSources(sources: readonly EngineOutputSource[]): Capability<never, unknown>;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function isEngineOutputSourceConsumer(value: unknown): value is EngineOutputSourceConsumer {
|
|
220
|
+
return typeof value === "object" && value !== null && typeof (value as EngineOutputSourceConsumer).withEngineOutputSources === "function";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The member's part of the consumer's change set: no entries, since the
|
|
225
|
+
* member's own pipeline plans and applies its changes, and a hole per output
|
|
226
|
+
* known only after apply, naming the member and the output.
|
|
227
|
+
*/
|
|
228
|
+
function outputsPart(member: string, planDigest: string, unknown: readonly string[]): ChangeSetPart {
|
|
229
|
+
const holes: ChangeSetHole[] = unknown.map((name) => ({
|
|
230
|
+
address: `${member}.${name}`,
|
|
231
|
+
type: "output",
|
|
232
|
+
reason: `${member}.${name} is known only after ${member} applies; what reads it is planned again then`,
|
|
233
|
+
}));
|
|
234
|
+
return {
|
|
235
|
+
member: { member: memberOutputsMemberName(member), lexicon: "chant", planner: "chant", status: "planned", planDigest, holes },
|
|
236
|
+
entries: [],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface MemberOutputsCapabilityOptions {
|
|
241
|
+
/** The engines' sources. Bound by the registry from the loaded plugins; empty until then. */
|
|
242
|
+
sources?: readonly EngineOutputSource[];
|
|
243
|
+
/** How a member is found. Default: {@link workspaceMemberLookup}. */
|
|
244
|
+
lookup?: MemberLookup;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** `member-outputs` over the given sources. */
|
|
248
|
+
export function memberOutputsCapability(
|
|
249
|
+
options: MemberOutputsCapabilityOptions = {},
|
|
250
|
+
): Capability<MemberOutputsInput, MemberOutputsOutput> & EngineOutputSourceConsumer {
|
|
251
|
+
const sources = options.sources ?? [];
|
|
252
|
+
const lookup = options.lookup ?? workspaceMemberLookup;
|
|
253
|
+
|
|
254
|
+
async function resolve(input: MemberOutputsInput): Promise<{ ref: EngineMemberRef; source: EngineOutputSource }> {
|
|
255
|
+
requireMember(input);
|
|
256
|
+
const ref = await lookup(input.cwd ?? process.cwd(), input.member);
|
|
257
|
+
const source = sources.find((s) => s.kinds.includes(ref.kind));
|
|
258
|
+
if (!source) throw new EngineOutputSourceMissingError(ref.member, ref.kind, [...new Set(sources.flatMap((s) => s.kinds))].sort());
|
|
259
|
+
return { ref, source };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
kind: MEMBER_OUTPUTS_KIND,
|
|
264
|
+
// It reads; the member's own pipeline applies. Nothing to compensate.
|
|
265
|
+
rollbackPolicy: "none-by-design",
|
|
266
|
+
targets: { refuse: "it reads another member's outputs; narrow that member's own plan instead" },
|
|
267
|
+
async plan(ctx: DeployContext, input: MemberOutputsInput): Promise<CapabilityPlan> {
|
|
268
|
+
const { ref, source } = await resolve(input);
|
|
269
|
+
const planned = await source.planned(ctx, ref);
|
|
270
|
+
const unknown = [...new Set(planned.unknown)].sort();
|
|
271
|
+
const known = Object.fromEntries(Object.entries(planned.known).filter(([name]) => !unknown.includes(name)));
|
|
272
|
+
const member = memberOutputsMemberName(ref.member);
|
|
273
|
+
const planDigest = computePlanDigest(MEMBER_OUTPUTS_KIND, { env: ctx.env, member: ref.member, kind: ref.kind, plan: planned.planDigest, known, unknown });
|
|
274
|
+
return { member, planDigest, changeSet: outputsPart(ref.member, planDigest, unknown), outputs: { known, unknown } };
|
|
275
|
+
},
|
|
276
|
+
async outputs(ctx: DeployContext, input: MemberOutputsInput): Promise<Record<string, unknown>> {
|
|
277
|
+
const { ref, source } = await resolve(input);
|
|
278
|
+
return source.applied(ctx, ref);
|
|
279
|
+
},
|
|
280
|
+
async run(ctx: DeployContext, input: MemberOutputsInput): Promise<MemberOutputsOutput> {
|
|
281
|
+
const { ref, source } = await resolve(input);
|
|
282
|
+
const now = await source.planned(ctx, ref);
|
|
283
|
+
if (now.changes) throw new MemberOutputsPendingError(ref.member);
|
|
284
|
+
return { member: ref.member, outputs: await source.applied(ctx, ref) };
|
|
285
|
+
},
|
|
286
|
+
withEngineOutputSources(next: readonly EngineOutputSource[]) {
|
|
287
|
+
return memberOutputsCapability({ ...options, sources: next }) as unknown as Capability<never, unknown>;
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** The starter set's `member-outputs`, with no sources until a registry binds the loaded plugins' ones. */
|
|
293
|
+
export const memberOutputsStarterCapability = memberOutputsCapability();
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* A component in the consumer's project that stands for workspace member
|
|
297
|
+
* `member` (#4455): one step reading its outputs. Named after the member, so
|
|
298
|
+
* `stackOutput(member, "<output>")` reads them and `dependsOn: [member]`
|
|
299
|
+
* orders the consumer after it.
|
|
300
|
+
*/
|
|
301
|
+
export function memberOutputsComponent(member: string, options: { cwd?: string } = {}): Component {
|
|
302
|
+
return {
|
|
303
|
+
name: member,
|
|
304
|
+
archetype: "infra",
|
|
305
|
+
dependsOn: [],
|
|
306
|
+
deploy: [{ phase: "Outputs", steps: [{ kind: MEMBER_OUTPUTS_KIND, member, ...(options.cwd ? { cwd: options.cwd } : {}) }] }],
|
|
307
|
+
};
|
|
308
|
+
}
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* `@<component>.publish.*`) resolves.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
+
import type { PlanShape } from "./covered-round";
|
|
30
31
|
import { assertTargetsAccepted, resolveStepInput, unresolvedWiringSources, type DriverComponent, type DriverPhase, type DriverStep } from "./driver";
|
|
31
32
|
import { componentPlanDigest } from "./gate-plan";
|
|
32
33
|
import { componentDeployInputs, type ComponentDeployInputs } from "./deploy-inputs";
|
|
@@ -44,7 +45,7 @@ import type { ChangeSetPart } from "../change-set";
|
|
|
44
45
|
* merges a run's outputs; absent when no step's plan says.
|
|
45
46
|
*/
|
|
46
47
|
export type WaveComponentPlan =
|
|
47
|
-
| { component: string; members: WaveMember[]; plans: Record<string, unknown>; parts: ChangeSetPart[]; outputs?: PlannedOutputs }
|
|
48
|
+
| { component: string; members: WaveMember[]; plans: Record<string, unknown>; parts: ChangeSetPart[]; outputs?: PlannedOutputs; shapes?: Record<string, PlanShape> }
|
|
48
49
|
| { component: string; error: string };
|
|
49
50
|
|
|
50
51
|
/** A part with no entries, for a member whose planner describes no changes. `unbound` steps become its holes (#4155). */
|
|
@@ -101,6 +102,7 @@ export async function planWaveComponent(
|
|
|
101
102
|
const plans: Record<string, unknown> = {};
|
|
102
103
|
const parts: ChangeSetPart[] = [];
|
|
103
104
|
let outputs: PlannedOutputs | undefined;
|
|
105
|
+
const shapes: Record<string, PlanShape> = {};
|
|
104
106
|
assertTargetsAccepted(component, registry, ctx);
|
|
105
107
|
for (const step of capabilitySteps(component.deploy)) {
|
|
106
108
|
const capability = registry.resolve(step.kind);
|
|
@@ -112,6 +114,7 @@ export async function planWaveComponent(
|
|
|
112
114
|
members.push(member);
|
|
113
115
|
parts.push(planned.changeSet ?? emptyPart(member));
|
|
114
116
|
if (planned.artifact !== undefined) plans[planned.member] = planned.artifact;
|
|
117
|
+
if (planned.shape) shapes[planned.member] = { ...planned.shape, member: planned.member };
|
|
115
118
|
if (planned.outputs) outputs = mergePlannedOutputs(outputs, planned.outputs);
|
|
116
119
|
}
|
|
117
120
|
// #4155: the steps without a plan, which no member above covers.
|
|
@@ -141,7 +144,7 @@ export async function planWaveComponent(
|
|
|
141
144
|
parts.push(emptyPart(member, read.unbound));
|
|
142
145
|
}
|
|
143
146
|
}
|
|
144
|
-
return { component: component.name, members, plans, parts, ...(outputs ? { outputs } : {}) };
|
|
147
|
+
return { component: component.name, members, plans, parts, ...(outputs ? { outputs } : {}), ...(Object.keys(shapes).length > 0 ? { shapes } : {}) };
|
|
145
148
|
} catch (err) {
|
|
146
149
|
return { component: component.name, error: err instanceof Error ? err.message : String(err) };
|
|
147
150
|
}
|
package/src/config.ts
CHANGED
|
@@ -34,6 +34,20 @@ export {
|
|
|
34
34
|
} from "./config-environments";
|
|
35
35
|
import { EXECUTOR_FORGES, isOwnershipParamRef, resolveOwnershipStack, type EnvironmentDeclaration, type OwnershipEnv } from "./config-environments";
|
|
36
36
|
|
|
37
|
+
/** An s3 index store's settings (#4519, ./lifecycle/snapshot-store-s3.ts). */
|
|
38
|
+
const S3StoreSettingsShape = {
|
|
39
|
+
bucket: z.string().min(1),
|
|
40
|
+
prefix: z.string().optional(),
|
|
41
|
+
region: z.string().min(1).optional(),
|
|
42
|
+
endpoint: z.string().url().optional(),
|
|
43
|
+
forcePathStyle: z.boolean().optional(),
|
|
44
|
+
credentials: z.object({
|
|
45
|
+
accessKeyId: z.string().min(1),
|
|
46
|
+
secretAccessKey: z.string().min(1),
|
|
47
|
+
sessionToken: z.string().min(1).optional(),
|
|
48
|
+
}).strict().optional(),
|
|
49
|
+
};
|
|
50
|
+
|
|
37
51
|
/**
|
|
38
52
|
* Zod schema for ChantConfig validation.
|
|
39
53
|
*/
|
|
@@ -139,6 +153,20 @@ export const ChantConfigSchema = z.object({
|
|
|
139
153
|
exclude: z.array(z.string().min(1)).optional(),
|
|
140
154
|
include: z.array(z.string().min(1)).optional(),
|
|
141
155
|
rootOnly: z.boolean().optional(),
|
|
156
|
+
// Strict: a misspelt key would send snapshots somewhere nobody looks (#4519).
|
|
157
|
+
snapshots: z.discriminatedUnion("store", [
|
|
158
|
+
z.object({ store: z.literal("git-ref"), ref: z.string().regex(/^refs\/chant\//).optional(), readOrphanBranch: z.boolean().optional() }).strict(),
|
|
159
|
+
z.object({ store: z.literal("orphan-branch") }).strict(),
|
|
160
|
+
z.object({ store: z.literal("local"), dir: z.string().min(1).optional() }).strict(),
|
|
161
|
+
z.object({ store: z.literal("s3"), ...S3StoreSettingsShape }).strict(),
|
|
162
|
+
z.object({ store: z.literal("none") }).strict(),
|
|
163
|
+
]).optional(),
|
|
164
|
+
observationBaseline: z.object({
|
|
165
|
+
store: z.enum(["file", "git-ref", "s3", "none"]),
|
|
166
|
+
dir: z.string().min(1).optional(),
|
|
167
|
+
ref: z.string().regex(/^refs\/chant\//).optional(),
|
|
168
|
+
s3: z.object(S3StoreSettingsShape).strict().optional(),
|
|
169
|
+
}).strict().optional(),
|
|
142
170
|
}).passthrough();
|
|
143
171
|
|
|
144
172
|
/**
|
|
@@ -504,6 +532,22 @@ export interface ChantConfig {
|
|
|
504
532
|
* in a project with no `chant.workspace.json`.
|
|
505
533
|
*/
|
|
506
534
|
rootOnly?: boolean;
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Where snapshots and every other index are kept (#4519): `git-ref`
|
|
538
|
+
* (default, `refs/chant/snapshots`, still reading `chant/lifecycle` for
|
|
539
|
+
* snapshots written before), `local` (a directory), `s3` (a bucket under a
|
|
540
|
+
* prefix), `none`, or `orphan-branch` (read-only). `CHANT_SNAPSHOT_STORE`
|
|
541
|
+
* and its companions override it. See `./lifecycle/snapshot-store.ts`.
|
|
542
|
+
*/
|
|
543
|
+
snapshots?: import("./lifecycle/snapshot-store").SnapshotStoreConfig;
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Where accepted observation deviations are kept (#4519): `file` (default,
|
|
547
|
+
* `.chant/observation-baseline/<env>.json`, committed and reviewed),
|
|
548
|
+
* `git-ref`, `s3` or `none`. See `./lifecycle/observation-baseline.ts`.
|
|
549
|
+
*/
|
|
550
|
+
observationBaseline?: import("./lifecycle/observation-baseline").ObservationBaselineStoreConfig;
|
|
507
551
|
}
|
|
508
552
|
|
|
509
553
|
/**
|
package/src/deep-observation.ts
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
import type { UnobservedEntity } from "./observation";
|
|
47
47
|
import { isHeldElsewhere, isNormalizedHeldElsewhere, normalizeHeldElsewhere } from "./held-elsewhere";
|
|
48
|
+
import { isSensitiveKey } from "./sensitive-key";
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
51
|
* A live property tree for one declared entity, already normalized by the
|
|
@@ -203,29 +204,10 @@ export const UNRESOLVED = "<chant:unresolved>";
|
|
|
203
204
|
/** Value a masked (secret-bearing) property takes. Matches the thin path's convention. */
|
|
204
205
|
export const MASKED = "[REDACTED]";
|
|
205
206
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
* its own drift signal.
|
|
211
|
-
*
|
|
212
|
-
* Shared with the thin snapshot path (`./lifecycle/snapshot.ts`), which warns
|
|
213
|
-
* on the same names — one list, so the two paths cannot disagree about what
|
|
214
|
-
* counts as a secret.
|
|
215
|
-
*/
|
|
216
|
-
export const SENSITIVE_KEY_PATTERNS: readonly RegExp[] = [
|
|
217
|
-
/password/i,
|
|
218
|
-
/secret/i,
|
|
219
|
-
/token/i,
|
|
220
|
-
/private.?key/i,
|
|
221
|
-
/credential/i,
|
|
222
|
-
/connection.?string/i,
|
|
223
|
-
];
|
|
224
|
-
|
|
225
|
-
/** True when a property name looks secret-bearing (see {@link SENSITIVE_KEY_PATTERNS}). */
|
|
226
|
-
export function isSensitiveKey(key: string): boolean {
|
|
227
|
-
return SENSITIVE_KEY_PATTERNS.some((p) => p.test(key));
|
|
228
|
-
}
|
|
207
|
+
// The secret-key list lives in `./sensitive-key.ts` so the drift record
|
|
208
|
+
// (`./drift-record.ts`) can redact with it inside the change-set bundle,
|
|
209
|
+
// which may not import this module's normalization code.
|
|
210
|
+
export { SENSITIVE_KEY_PATTERNS, isSensitiveKey } from "./sensitive-key";
|
|
229
211
|
|
|
230
212
|
/** Which tree a hook is being invoked on. */
|
|
231
213
|
export type DeepSide = "declared" | "live";
|