@intentius/chant 0.124.52 → 0.124.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/discover.js +3 -1
- package/dist/behaviour.d.ts +1 -1
- package/dist/behaviour.d.ts.map +1 -1
- package/dist/capabilities/templates.d.ts +65 -0
- package/dist/capabilities/templates.d.ts.map +1 -0
- package/dist/capabilities/templates.js +193 -0
- package/dist/capabilities/templates.json +145 -0
- package/dist/change-set-graph.d.ts +35 -3
- package/dist/change-set-graph.d.ts.map +1 -1
- package/dist/change-set-graph.js +60 -9
- package/dist/change-set.d.ts +65 -7
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +23 -12
- package/dist/ci-identity.d.ts +7 -2
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +4 -1
- package/dist/cli/commands/import-index.d.ts +52 -0
- package/dist/cli/commands/import-index.d.ts.map +1 -0
- package/dist/cli/commands/import-index.js +226 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +4 -2
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/components.js +11 -2
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +16 -3
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +22 -4
- package/dist/cli/handlers/index-cloud.d.ts +14 -0
- package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
- package/dist/cli/handlers/index-cloud.js +22 -0
- package/dist/cli/handlers/index-diff.d.ts +18 -0
- package/dist/cli/handlers/index-diff.d.ts.map +1 -0
- package/dist/cli/handlers/index-diff.js +142 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-drift.js +64 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +188 -28
- package/dist/cli/handlers/misc.d.ts +9 -0
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +77 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +15 -2
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +89 -15
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +28 -4
- package/dist/cli/handlers/scenario.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.js +8 -7
- package/dist/cli/handlers/search.d.ts +34 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +216 -36
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +55 -1
- package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
- package/dist/cli/mcp/lifecycle-tools.js +3 -2
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.js +4 -3
- package/dist/cli/mcp/workspace-tools.js +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +55 -2
- package/dist/cli/registry.d.ts +19 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cloud-lister/aws.d.ts +35 -0
- package/dist/cloud-lister/aws.d.ts.map +1 -0
- package/dist/cloud-lister/aws.js +203 -0
- package/dist/cloud-lister/azure.d.ts +34 -0
- package/dist/cloud-lister/azure.d.ts.map +1 -0
- package/dist/cloud-lister/azure.js +129 -0
- package/dist/cloud-lister/common.d.ts +40 -0
- package/dist/cloud-lister/common.d.ts.map +1 -0
- package/dist/cloud-lister/common.js +83 -0
- package/dist/cloud-lister/credentials.d.ts +50 -0
- package/dist/cloud-lister/credentials.d.ts.map +1 -0
- package/dist/cloud-lister/credentials.js +86 -0
- package/dist/cloud-lister/fakes.d.ts +27 -0
- package/dist/cloud-lister/fakes.d.ts.map +1 -0
- package/dist/cloud-lister/fakes.js +50 -0
- package/dist/cloud-lister/gcp.d.ts +42 -0
- package/dist/cloud-lister/gcp.d.ts.map +1 -0
- package/dist/cloud-lister/gcp.js +143 -0
- package/dist/cloud-lister/guard.d.ts +49 -0
- package/dist/cloud-lister/guard.d.ts.map +1 -0
- package/dist/cloud-lister/guard.js +103 -0
- package/dist/cloud-lister/ids.d.ts +23 -0
- package/dist/cloud-lister/ids.d.ts.map +1 -0
- package/dist/cloud-lister/ids.js +42 -0
- package/dist/cloud-lister/index.d.ts +67 -0
- package/dist/cloud-lister/index.d.ts.map +1 -0
- package/dist/cloud-lister/index.js +102 -0
- package/dist/cloud-lister/kubernetes.d.ts +64 -0
- package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
- package/dist/cloud-lister/kubernetes.js +276 -0
- package/dist/cloud-lister/markers.d.ts +31 -0
- package/dist/cloud-lister/markers.d.ts.map +1 -0
- package/dist/cloud-lister/markers.js +73 -0
- package/dist/cloud-lister/mask.d.ts +31 -0
- package/dist/cloud-lister/mask.d.ts.map +1 -0
- package/dist/cloud-lister/mask.js +74 -0
- package/dist/cloud-lister/sigv4.d.ts +29 -0
- package/dist/cloud-lister/sigv4.d.ts.map +1 -0
- package/dist/cloud-lister/sigv4.js +54 -0
- package/dist/cloud-lister/transport.d.ts +55 -0
- package/dist/cloud-lister/transport.d.ts.map +1 -0
- package/dist/cloud-lister/transport.js +86 -0
- package/dist/cloud-lister/types.d.ts +121 -0
- package/dist/cloud-lister/types.d.ts.map +1 -0
- package/dist/cloud-lister/types.js +24 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +6 -0
- package/dist/components/capability-plugin.d.ts +9 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability.d.ts +215 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +21 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +58 -14
- package/dist/components/declared-reads.d.ts +38 -0
- package/dist/components/declared-reads.d.ts.map +1 -0
- package/dist/components/declared-reads.js +131 -0
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/deploy-units.js +4 -0
- package/dist/components/driver.d.ts +34 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +66 -3
- package/dist/components/fan-out-run.d.ts +23 -1
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +82 -14
- package/dist/components/fan-out-support.d.ts +6 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +4 -1
- package/dist/components/fan-out.d.ts +28 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/groups.d.ts +229 -0
- package/dist/components/groups.d.ts.map +1 -0
- package/dist/components/groups.js +760 -0
- package/dist/components/index.d.ts +6 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -0
- package/dist/components/plan-shape-conformance.d.ts +48 -0
- package/dist/components/plan-shape-conformance.d.ts.map +1 -0
- package/dist/components/plan-shape-conformance.js +122 -0
- package/dist/components/pr-run.d.ts +41 -4
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +209 -39
- package/dist/components/promote-rounds.d.ts +9 -0
- package/dist/components/promote-rounds.d.ts.map +1 -1
- package/dist/components/promote-rounds.js +23 -2
- package/dist/components/promote.d.ts +17 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +36 -6
- package/dist/components/release-gate.d.ts +23 -1
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +97 -33
- package/dist/components/run-groups.d.ts +58 -0
- package/dist/components/run-groups.d.ts.map +1 -0
- package/dist/components/run-groups.js +78 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +6 -1
- package/dist/components/verbs/index.d.ts +1 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +1 -0
- package/dist/components/verbs/member-outputs.d.ts +187 -0
- package/dist/components/verbs/member-outputs.d.ts.map +1 -0
- package/dist/components/verbs/member-outputs.js +218 -0
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -0
- package/dist/deep-observation.d.ts +1 -13
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/deep-observation.js +5 -22
- package/dist/drift-record.d.ts +542 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +886 -0
- package/dist/engine-facts.d.ts +144 -0
- package/dist/engine-facts.d.ts.map +1 -0
- package/dist/engine-facts.js +14 -0
- package/dist/graph-ir.d.ts +153 -2
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +224 -6
- package/dist/graph-refs.d.ts +145 -0
- package/dist/graph-refs.d.ts.map +1 -1
- package/dist/graph-refs.js +305 -0
- package/dist/graph-schema/fixtures/adopt.json +15 -0
- package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/dist/graph-schema/fixtures/index.json +68 -0
- package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/dist/graph-schema/fixtures.d.ts +23 -0
- package/dist/graph-schema/fixtures.d.ts.map +1 -0
- package/dist/graph-schema/fixtures.js +246 -0
- package/dist/graph-schema/generate.d.ts +15 -0
- package/dist/graph-schema/generate.d.ts.map +1 -0
- package/dist/graph-schema/generate.js +267 -0
- package/dist/graph-schema/graph.schema.json +1387 -0
- package/dist/health.schema.json +2 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/instance-nodes.d.ts +159 -0
- package/dist/instance-nodes.d.ts.map +1 -0
- package/dist/instance-nodes.js +231 -0
- package/dist/lexicon.d.ts +72 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/adoption-file.d.ts +56 -0
- package/dist/lifecycle/adoption-file.d.ts.map +1 -0
- package/dist/lifecycle/adoption-file.js +123 -0
- package/dist/lifecycle/cloud-index.d.ts +186 -0
- package/dist/lifecycle/cloud-index.d.ts.map +1 -0
- package/dist/lifecycle/cloud-index.js +618 -0
- package/dist/lifecycle/derive-edges.d.ts +59 -0
- package/dist/lifecycle/derive-edges.d.ts.map +1 -0
- package/dist/lifecycle/derive-edges.js +126 -0
- package/dist/lifecycle/git.d.ts +10 -9
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +7 -1
- package/dist/lifecycle/identity-match.d.ts +133 -0
- package/dist/lifecycle/identity-match.d.ts.map +1 -0
- package/dist/lifecycle/identity-match.js +147 -0
- package/dist/lifecycle/index-diff.d.ts +224 -0
- package/dist/lifecycle/index-diff.d.ts.map +1 -0
- package/dist/lifecycle/index-diff.js +327 -0
- package/dist/lifecycle/index-document.d.ts +130 -0
- package/dist/lifecycle/index-document.d.ts.map +1 -0
- package/dist/lifecycle/index-document.js +189 -0
- package/dist/lifecycle/index-drift.d.ts +121 -0
- package/dist/lifecycle/index-drift.d.ts.map +1 -0
- package/dist/lifecycle/index-drift.js +178 -0
- package/dist/lifecycle/index-import.d.ts +168 -0
- package/dist/lifecycle/index-import.d.ts.map +1 -0
- package/dist/lifecycle/index-import.js +515 -0
- package/dist/lifecycle/index-join.d.ts +129 -0
- package/dist/lifecycle/index-join.d.ts.map +1 -0
- package/dist/lifecycle/index-join.js +248 -0
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/observation-baseline.d.ts +61 -21
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/observation-baseline.js +144 -19
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +6 -1
- package/dist/lifecycle/query.d.ts +45 -4
- package/dist/lifecycle/query.d.ts.map +1 -1
- package/dist/lifecycle/query.js +89 -7
- package/dist/lifecycle/replay.d.ts +10 -3
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/replay.js +10 -6
- package/dist/lifecycle/scenario.d.ts +4 -3
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
- package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-fake.js +21 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-s3.js +254 -0
- package/dist/lifecycle/snapshot-store.d.ts +170 -0
- package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store.js +832 -0
- package/dist/lifecycle/snapshot.d.ts +11 -0
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +18 -5
- package/dist/lifecycle/type-listing.d.ts +148 -0
- package/dist/lifecycle/type-listing.d.ts.map +1 -0
- package/dist/lifecycle/type-listing.js +166 -0
- package/dist/lint/component-checks.d.ts +15 -1
- package/dist/lint/component-checks.d.ts.map +1 -1
- package/dist/lint/component-checks.js +1 -0
- package/dist/lint/presets/relaxed.json +2 -1
- package/dist/lint/presets/strict.json +2 -1
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
- package/dist/lint/rules/comp/index.d.ts +2 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/comp/index.js +4 -1
- package/dist/observation.d.ts +19 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +18 -1
- package/dist/op/activities/activity-contracts.d.ts +1 -1
- package/dist/op/activities/activity-contracts.js +1 -1
- package/dist/op/activities/change-set.d.ts +6 -4
- package/dist/op/activities/change-set.d.ts.map +1 -1
- package/dist/op/activities/change-set.js +4 -2
- package/dist/op/activity-failure.d.ts +27 -0
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-failure.js +31 -0
- package/dist/op/index.d.ts +2 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +5 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -8
- package/dist/pr-loop.d.ts +56 -1
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +51 -1
- package/dist/pulumi-urn.d.ts +101 -0
- package/dist/pulumi-urn.d.ts.map +1 -0
- package/dist/pulumi-urn.js +131 -0
- package/dist/release-floors.d.ts +23 -0
- package/dist/release-floors.d.ts.map +1 -0
- package/dist/release-floors.js +22 -0
- package/dist/sensitive-key.d.ts +23 -0
- package/dist/sensitive-key.d.ts.map +1 -0
- package/dist/sensitive-key.js +31 -0
- package/dist/wave-runtime.mjs +234 -234
- package/dist/workspace/adoption-report.d.ts +53 -0
- package/dist/workspace/adoption-report.d.ts.map +1 -0
- package/dist/workspace/adoption-report.js +76 -0
- package/dist/workspace/capabilities-contract.d.ts +7 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -1
- package/dist/workspace/capabilities-contract.js +2 -1
- package/dist/workspace/capabilities.schema.json +98 -0
- package/dist/workspace/change-set.schema.json +42 -3
- package/dist/workspace/ci-cloud-identity.d.ts +13 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +30 -1
- package/dist/workspace/compose-graph.d.ts +10 -1
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +6 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +6 -1
- package/dist/workspace/drift-cli.d.ts +81 -0
- package/dist/workspace/drift-cli.d.ts.map +1 -0
- package/dist/workspace/drift-cli.js +317 -0
- package/dist/workspace/drift.schema.json +672 -0
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +3 -0
- package/dist/workspace/graph.schema.json +72 -0
- package/dist/workspace/init.d.ts +5 -1
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/init.js +55 -9
- package/dist/workspace/ledger.schema.json +1 -1
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +2 -0
- package/dist/workspace/pr-report.schema.json +51 -2
- package/dist/workspace/promote-waves.d.ts +112 -0
- package/dist/workspace/promote-waves.d.ts.map +1 -0
- package/dist/workspace/promote-waves.js +290 -0
- package/dist/workspace/promote.d.ts +84 -2
- package/dist/workspace/promote.d.ts.map +1 -1
- package/dist/workspace/promote.js +347 -61
- package/dist/workspace/reason-codes.d.ts +12 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +14 -0
- package/dist/workspace/search-contract.d.ts +3 -3
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/search-contract.js +2 -0
- package/dist/workspace/search.schema.json +103 -5
- package/package.json +14 -1
- package/src/__fixtures__/drift-record/README.md +58 -0
- package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
- package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
- package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
- package/src/__fixtures__/drift-record/component-status.json +91 -0
- package/src/__fixtures__/drift-record/deep-diff.json +259 -0
- package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
- package/src/__fixtures__/drift-record/live-diff.json +125 -0
- package/src/audit/discover.e2e.test.ts +14 -0
- package/src/audit/discover.ts +3 -1
- package/src/behaviour.ts +1 -1
- package/src/capabilities/templates.json +145 -0
- package/src/capabilities/templates.test.ts +212 -0
- package/src/capabilities/templates.ts +204 -0
- package/src/change-set-bundle.test.ts +83 -0
- package/src/change-set-graph.ts +80 -9
- package/src/change-set.test.ts +24 -0
- package/src/change-set.ts +77 -14
- package/src/ci-identity-targets.test.ts +23 -0
- package/src/ci-identity.ts +10 -4
- package/src/cli/commands/import-index.test.ts +117 -0
- package/src/cli/commands/import-index.ts +265 -0
- package/src/cli/commands/lint.ts +4 -2
- package/src/cli/handlers/components.test.ts +9 -0
- package/src/cli/handlers/components.ts +12 -2
- package/src/cli/handlers/fan-out.ts +17 -3
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +26 -4
- package/src/cli/handlers/index-cloud.ts +32 -0
- package/src/cli/handlers/index-diff.test.ts +68 -0
- package/src/cli/handlers/index-diff.ts +146 -0
- package/src/cli/handlers/ledger-root-member.test.ts +161 -0
- package/src/cli/handlers/lifecycle-drift.ts +68 -0
- package/src/cli/handlers/lifecycle.test.ts +33 -11
- package/src/cli/handlers/lifecycle.ts +205 -30
- package/src/cli/handlers/misc.ts +73 -0
- package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
- package/src/cli/handlers/pr.ts +14 -2
- package/src/cli/handlers/promote.test.ts +64 -0
- package/src/cli/handlers/promote.ts +81 -6
- package/src/cli/handlers/query.ts +27 -4
- package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
- package/src/cli/handlers/run-groups-cli.test.ts +229 -0
- package/src/cli/handlers/scenario.test.ts +13 -0
- package/src/cli/handlers/scenario.ts +8 -7
- package/src/cli/handlers/search-index.test.ts +255 -0
- package/src/cli/handlers/search-json.test.ts +1 -1
- package/src/cli/handlers/search-ownership.test.ts +194 -0
- package/src/cli/handlers/search.ts +271 -38
- package/src/cli/main.test.ts +7 -0
- package/src/cli/main.ts +56 -1
- package/src/cli/mcp/lifecycle-tools.ts +3 -2
- package/src/cli/mcp/resource-handlers.ts +4 -3
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/cli/parse-args.ts +40 -2
- package/src/cli/registry.ts +19 -1
- package/src/cloud-lister/aws.test.ts +202 -0
- package/src/cloud-lister/aws.ts +226 -0
- package/src/cloud-lister/azure.test.ts +132 -0
- package/src/cloud-lister/azure.ts +142 -0
- package/src/cloud-lister/common.ts +115 -0
- package/src/cloud-lister/credentials.ts +110 -0
- package/src/cloud-lister/fakes.ts +66 -0
- package/src/cloud-lister/gcp.test.ts +121 -0
- package/src/cloud-lister/gcp.ts +159 -0
- package/src/cloud-lister/guard.test.ts +198 -0
- package/src/cloud-lister/guard.ts +111 -0
- package/src/cloud-lister/ids.ts +54 -0
- package/src/cloud-lister/index.ts +134 -0
- package/src/cloud-lister/kubernetes.test.ts +160 -0
- package/src/cloud-lister/kubernetes.ts +284 -0
- package/src/cloud-lister/markers.ts +74 -0
- package/src/cloud-lister/mask.ts +77 -0
- package/src/cloud-lister/shape.test.ts +81 -0
- package/src/cloud-lister/sigv4.ts +70 -0
- package/src/cloud-lister/transport.ts +118 -0
- package/src/cloud-lister/types.ts +135 -0
- package/src/components/capability-plugin-loader.ts +4 -0
- package/src/components/capability-plugin.ts +10 -0
- package/src/components/capability.ts +229 -0
- package/src/components/cli-support.ts +70 -14
- package/src/components/declared-reads.test.ts +103 -0
- package/src/components/declared-reads.ts +134 -0
- package/src/components/deploy-units.ts +4 -0
- package/src/components/driver.ts +89 -4
- package/src/components/fan-out-run.ts +95 -15
- package/src/components/fan-out-support.ts +10 -1
- package/src/components/fan-out.ts +30 -0
- package/src/components/groups.test.ts +320 -0
- package/src/components/groups.ts +880 -0
- package/src/components/index.ts +42 -0
- package/src/components/plan-shape-conformance.ts +130 -0
- package/src/components/pr-env-run-bundle.test.ts +2 -0
- package/src/components/pr-run.test.ts +516 -3
- package/src/components/pr-run.ts +231 -41
- package/src/components/promote-rounds.ts +25 -2
- package/src/components/promote.ts +55 -5
- package/src/components/registry.test.ts +1 -1
- package/src/components/release-gate.ts +115 -30
- package/src/components/release-groups.test.ts +362 -0
- package/src/components/run-groups.ts +113 -0
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +1 -0
- package/src/components/verbs/member-outputs.test.ts +205 -0
- package/src/components/verbs/member-outputs.ts +308 -0
- package/src/config.ts +44 -0
- package/src/deep-observation.ts +5 -23
- package/src/drift-record.test.ts +596 -0
- package/src/drift-record.ts +1206 -0
- package/src/engine-facts.guard.test.ts +111 -0
- package/src/engine-facts.test.ts +178 -0
- package/src/engine-facts.ts +154 -0
- package/src/gated-waves-bundle.test.ts +2 -0
- package/src/graph-ir-live.test.ts +92 -0
- package/src/graph-ir.ts +317 -7
- package/src/graph-refs.ts +416 -0
- package/src/graph-schema/fixtures/adopt.json +15 -0
- package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/src/graph-schema/fixtures/index.json +68 -0
- package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/src/graph-schema/fixtures.ts +267 -0
- package/src/graph-schema/generate.ts +263 -0
- package/src/graph-schema/graph-schema.test.ts +153 -0
- package/src/graph-schema/graph.schema.json +1387 -0
- package/src/health.schema.json +2 -1
- package/src/identity.ts +2 -0
- package/src/index.ts +2 -0
- package/src/instance-nodes.ts +288 -0
- package/src/lexicon.ts +76 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
- package/src/lifecycle/adoption-file.test.ts +100 -0
- package/src/lifecycle/adoption-file.ts +154 -0
- package/src/lifecycle/cloud-index.test.ts +517 -0
- package/src/lifecycle/cloud-index.ts +736 -0
- package/src/lifecycle/derive-edges.test.ts +193 -0
- package/src/lifecycle/derive-edges.ts +182 -0
- package/src/lifecycle/git.ts +10 -4
- package/src/lifecycle/identity-match.test.ts +308 -0
- package/src/lifecycle/identity-match.ts +257 -0
- package/src/lifecycle/index-diff.test.ts +198 -0
- package/src/lifecycle/index-diff.ts +476 -0
- package/src/lifecycle/index-document.test.ts +64 -0
- package/src/lifecycle/index-document.ts +299 -0
- package/src/lifecycle/index-drift.test.ts +191 -0
- package/src/lifecycle/index-drift.ts +304 -0
- package/src/lifecycle/index-import.test.ts +255 -0
- package/src/lifecycle/index-import.ts +634 -0
- package/src/lifecycle/index-join.test.ts +250 -0
- package/src/lifecycle/index-join.ts +304 -0
- package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/member-ledger.test.ts +25 -7
- package/src/lifecycle/observation-baseline.test.ts +72 -1
- package/src/lifecycle/observation-baseline.ts +190 -27
- package/src/lifecycle/observe.ts +6 -1
- package/src/lifecycle/query.test.ts +69 -1
- package/src/lifecycle/query.ts +103 -10
- package/src/lifecycle/replay.test.ts +9 -5
- package/src/lifecycle/replay.ts +9 -5
- package/src/lifecycle/scenario.ts +4 -3
- package/src/lifecycle/snapshot-store-fake.ts +29 -0
- package/src/lifecycle/snapshot-store-s3.ts +316 -0
- package/src/lifecycle/snapshot-store.test.ts +385 -0
- package/src/lifecycle/snapshot-store.ts +981 -0
- package/src/lifecycle/snapshot.test.ts +13 -0
- package/src/lifecycle/snapshot.ts +23 -6
- package/src/lifecycle/type-listing.test.ts +166 -0
- package/src/lifecycle/type-listing.ts +309 -0
- package/src/lint/component-checks.ts +17 -1
- package/src/lint/config.test.ts +3 -0
- package/src/lint/presets/relaxed.json +2 -1
- package/src/lint/presets/strict.json +2 -1
- package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
- package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
- package/src/lint/rules/comp/comp.test.ts +2 -1
- package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
- package/src/lint/rules/comp/comp008.test.ts +77 -0
- package/src/lint/rules/comp/index.ts +4 -1
- package/src/observation-edges.test.ts +33 -0
- package/src/observation.ts +36 -1
- package/src/op/activities/activity-contracts.ts +1 -1
- package/src/op/activities/change-set.ts +8 -5
- package/src/op/activity-failure.ts +36 -0
- package/src/op/index.ts +2 -1
- package/src/op/local-executor.test.ts +80 -0
- package/src/op/local-executor.ts +27 -9
- package/src/pr-loop-bundle.test.ts +5 -2
- package/src/pr-loop.test.ts +43 -0
- package/src/pr-loop.ts +111 -2
- package/src/pulumi-urn.ts +151 -0
- package/src/release-floors.ts +22 -0
- package/src/sensitive-key.ts +34 -0
- package/src/workspace/adoption-report.test.ts +61 -0
- package/src/workspace/adoption-report.ts +103 -0
- package/src/workspace/capabilities-contract.test.ts +4 -0
- package/src/workspace/capabilities-contract.ts +9 -1
- package/src/workspace/capabilities.schema.json +98 -0
- package/src/workspace/change-set.schema.json +42 -3
- package/src/workspace/ci-cloud-identity.ts +31 -1
- package/src/workspace/compose-graph.test.ts +12 -0
- package/src/workspace/compose-graph.ts +12 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.ts +6 -1
- package/src/workspace/drift-cli.test.ts +65 -0
- package/src/workspace/drift-cli.ts +365 -0
- package/src/workspace/drift.schema.json +672 -0
- package/src/workspace/graph-cli.ts +5 -0
- package/src/workspace/graph.schema.json +72 -0
- package/src/workspace/init.test.ts +38 -1
- package/src/workspace/init.ts +50 -8
- package/src/workspace/intent-gaps.test.ts +18 -2
- package/src/workspace/ledger.schema.json +1 -1
- package/src/workspace/nested-graph.ts +1 -0
- package/src/workspace/pr-report.schema.json +51 -2
- package/src/workspace/promote-waves.test.ts +296 -0
- package/src/workspace/promote-waves.ts +340 -0
- package/src/workspace/promote.test.ts +158 -4
- package/src/workspace/promote.ts +398 -57
- package/src/workspace/reason-codes.test.ts +4 -1
- package/src/workspace/reason-codes.ts +14 -0
- package/src/workspace/search-contract.ts +2 -0
- package/src/workspace/search.schema.json +103 -5
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Investigation templates (#4531): the capability catalog's second kind of
|
|
3
|
+
* entry, after the capabilities themselves (behold#1022 contract 3).
|
|
4
|
+
*
|
|
5
|
+
* A template is a fixed query with the resource filled in, so a click in
|
|
6
|
+
* behold, choudoufu's `investigate` (choudoufu#2280) and an agent run the
|
|
7
|
+
* same query. Each says which language it expands to (behold#1022 ruling Q4):
|
|
8
|
+
* a `chant search` path pattern where one can express the question, Cypher
|
|
9
|
+
* otherwise. A path template needs nothing beyond chant search, so it keeps
|
|
10
|
+
* working where behold's optional LadybugDB peer is missing; a Cypher one
|
|
11
|
+
* needs behold's graph pane.
|
|
12
|
+
*
|
|
13
|
+
* The catalog lives here, beside the capability rules (ruling Q5), and is
|
|
14
|
+
* data: `templates.json` beside this module, shipped as
|
|
15
|
+
* `@intentius/chant/capabilities/templates.json` and carried as `templates`
|
|
16
|
+
* on the capabilities document (`chant search --capabilities --json`).
|
|
17
|
+
* behold and choudoufu read it as data. The initial set is the one behold#1027
|
|
18
|
+
* stubbed in behold's src/graph-db/templates.ts.
|
|
19
|
+
*
|
|
20
|
+
* Filling in: a path template's `{{<input>}}` is replaced by the input's
|
|
21
|
+
* value, the resource's id for `{{node}}`. A Cypher template takes its inputs
|
|
22
|
+
* as query parameters, never spliced into the text: the resource's key is
|
|
23
|
+
* `$key` and every other input is `$<name>`.
|
|
24
|
+
*/
|
|
25
|
+
import { NEXT_RELEASE } from "../release-floors";
|
|
26
|
+
import data from "./templates.json" with { type: "json" };
|
|
27
|
+
|
|
28
|
+
/** The first chant whose capabilities document carries `templates` (#4531). */
|
|
29
|
+
export const CAPABILITY_TEMPLATES_FLOOR = NEXT_RELEASE;
|
|
30
|
+
|
|
31
|
+
/** The version of templates.json's shape. */
|
|
32
|
+
export const TEMPLATES_DATA_VERSION = 1;
|
|
33
|
+
|
|
34
|
+
export type TemplateQueryKind = "path" | "cypher";
|
|
35
|
+
|
|
36
|
+
/** What answering reads: the declared source, a live read, recorded snapshots, or only the session's loaded graph. */
|
|
37
|
+
export type TemplateReads = "source" | "live" | "snapshots" | "session";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* resource: the node the template is about, given as a name, an IR id, a
|
|
41
|
+
* block address or a cloud ID, and resolved to one node before expanding.
|
|
42
|
+
* scope: an environment or estate the query is read in. term: one search
|
|
43
|
+
* term, such as a kind or a capability call.
|
|
44
|
+
*/
|
|
45
|
+
export type TemplateInputKind = "resource" | "scope" | "term";
|
|
46
|
+
|
|
47
|
+
export interface TemplateInput {
|
|
48
|
+
name: string;
|
|
49
|
+
kind: TemplateInputKind;
|
|
50
|
+
description: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface TemplateEntry {
|
|
54
|
+
id: string;
|
|
55
|
+
title: string;
|
|
56
|
+
/** The question it answers, in people's words. */
|
|
57
|
+
question: string;
|
|
58
|
+
/** The sources whose nodes it applies to (behold's source kinds); absent means every source. */
|
|
59
|
+
sources?: string[];
|
|
60
|
+
inputs: TemplateInput[];
|
|
61
|
+
reads: TemplateReads;
|
|
62
|
+
query: { kind: TemplateQueryKind; text: string };
|
|
63
|
+
/** What it does not answer, said where it is offered. */
|
|
64
|
+
note?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A templates file that does not hold to the entry kind, naming the template. */
|
|
68
|
+
export class TemplateCatalogError extends Error {}
|
|
69
|
+
|
|
70
|
+
const ID = /^[a-z][a-z0-9-]*$/;
|
|
71
|
+
const INPUT_NAME = /^[a-z][A-Za-z0-9_]*$/;
|
|
72
|
+
const KINDS: readonly TemplateQueryKind[] = ["path", "cypher"];
|
|
73
|
+
const READS: readonly TemplateReads[] = ["source", "live", "snapshots", "session"];
|
|
74
|
+
const INPUT_KINDS: readonly TemplateInputKind[] = ["resource", "scope", "term"];
|
|
75
|
+
const PLACEHOLDER = /\{\{([^{}]*)\}\}/g;
|
|
76
|
+
const PARAM = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
77
|
+
|
|
78
|
+
const str = (v: unknown): v is string => typeof v === "string" && v.trim() !== "";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Read a templates file, checking each entry against the entry kind: a
|
|
82
|
+
* unique id, exactly one resource input first, a query of a known kind
|
|
83
|
+
* whose placeholders (path) or parameters (Cypher) are its inputs, and every
|
|
84
|
+
* input used.
|
|
85
|
+
*/
|
|
86
|
+
export function parseTemplates(raw: unknown): TemplateEntry[] {
|
|
87
|
+
const doc = raw as { version?: unknown; templates?: unknown };
|
|
88
|
+
if (!doc || typeof doc !== "object") throw new TemplateCatalogError("a templates file is an object");
|
|
89
|
+
if (doc.version !== TEMPLATES_DATA_VERSION) throw new TemplateCatalogError(`templates file version ${String(doc.version)} is not ${TEMPLATES_DATA_VERSION}`);
|
|
90
|
+
if (!Array.isArray(doc.templates)) throw new TemplateCatalogError("templates is a list");
|
|
91
|
+
const seen = new Set<string>();
|
|
92
|
+
return doc.templates.map((t, i) => {
|
|
93
|
+
const e = t as Partial<TemplateEntry>;
|
|
94
|
+
const where = str(e?.id) ? `template ${e.id}` : `template ${i}`;
|
|
95
|
+
const fail = (m: string): never => {
|
|
96
|
+
throw new TemplateCatalogError(`${where}: ${m}`);
|
|
97
|
+
};
|
|
98
|
+
if (!e || typeof e !== "object") fail("is not an object");
|
|
99
|
+
if (!str(e.id) || !ID.test(e.id)) fail("needs an id of lowercase letters, digits and -");
|
|
100
|
+
if (seen.has(e.id!)) fail("is defined twice");
|
|
101
|
+
seen.add(e.id!);
|
|
102
|
+
if (!str(e.title)) fail("needs a title");
|
|
103
|
+
if (!str(e.question)) fail("needs the question it answers");
|
|
104
|
+
if (!READS.includes(e.reads as TemplateReads)) fail(`reads is one of ${READS.join(", ")}`);
|
|
105
|
+
if (e.sources !== undefined && (!Array.isArray(e.sources) || !e.sources.every(str))) fail("sources is a list of source kinds");
|
|
106
|
+
if (e.note !== undefined && !str(e.note)) fail("a note is text");
|
|
107
|
+
if (!Array.isArray(e.inputs)) fail("needs its inputs");
|
|
108
|
+
const names = new Set<string>();
|
|
109
|
+
for (const input of e.inputs!) {
|
|
110
|
+
if (!input || !str(input.name) || !INPUT_NAME.test(input.name)) fail("an input needs a name");
|
|
111
|
+
if (names.has(input.name)) fail(`input ${input.name} is listed twice`);
|
|
112
|
+
names.add(input.name);
|
|
113
|
+
if (!INPUT_KINDS.includes(input.kind)) fail(`input ${input.name}'s kind is one of ${INPUT_KINDS.join(", ")}`);
|
|
114
|
+
if (!str(input.description)) fail(`input ${input.name} needs a description`);
|
|
115
|
+
}
|
|
116
|
+
const resources = e.inputs!.filter((x) => x.kind === "resource");
|
|
117
|
+
if (resources.length !== 1 || e.inputs![0] !== resources[0]) fail("takes one resource input, first");
|
|
118
|
+
const q = e.query;
|
|
119
|
+
if (!q || !KINDS.includes(q.kind) || !str(q.text)) fail(`query is { kind: ${KINDS.join(" | ")}, text }`);
|
|
120
|
+
const resource = resources[0].name;
|
|
121
|
+
if (q!.kind === "path") {
|
|
122
|
+
if (/\$[A-Za-z_]/.test(q!.text)) fail("a path pattern takes {{input}} placeholders, not $parameters");
|
|
123
|
+
const used = new Set([...q!.text.matchAll(PLACEHOLDER)].map((m) => m[1]));
|
|
124
|
+
for (const u of used) if (!names.has(u)) fail(`{{${u}}} names no input`);
|
|
125
|
+
// A scope is where the query is read, not part of its text.
|
|
126
|
+
for (const input of e.inputs!) if (input.kind !== "scope" && !used.has(input.name)) fail(`the path pattern never uses ${input.name}`);
|
|
127
|
+
} else {
|
|
128
|
+
if (/\{\{/.test(q!.text)) fail("a Cypher query takes $parameters, never spliced {{input}} text");
|
|
129
|
+
if (!/^\s*(MATCH|OPTIONAL MATCH|WITH|CALL|UNWIND)\b/i.test(q!.text)) fail("a Cypher query starts with a reading clause");
|
|
130
|
+
if (/\b(CREATE|MERGE|DELETE|DETACH|SET|REMOVE|DROP)\b/i.test(q!.text)) fail("a Cypher query only reads");
|
|
131
|
+
if (!balanced(q!.text)) fail("its brackets do not balance");
|
|
132
|
+
const params = new Set([...q!.text.matchAll(PARAM)].map((m) => m[1]));
|
|
133
|
+
if (!params.has("key")) fail("a Cypher query binds the resource as $key");
|
|
134
|
+
for (const p of params) if (p !== "key" && !names.has(p)) fail(`$${p} names no input`);
|
|
135
|
+
for (const input of e.inputs!) if (input.name !== resource && input.kind !== "scope" && !params.has(input.name)) fail(`the query never uses $${input.name}`);
|
|
136
|
+
}
|
|
137
|
+
return e as TemplateEntry;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Whether (), [] and {} nest, outside quoted strings. */
|
|
142
|
+
function balanced(text: string): boolean {
|
|
143
|
+
const open: string[] = [];
|
|
144
|
+
const pair: Record<string, string> = { ")": "(", "]": "[", "}": "{" };
|
|
145
|
+
let quote: string | undefined;
|
|
146
|
+
for (const c of text) {
|
|
147
|
+
if (quote) {
|
|
148
|
+
if (c === quote) quote = undefined;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (c === "'" || c === '"') quote = c;
|
|
152
|
+
else if (c === "(" || c === "[" || c === "{") open.push(c);
|
|
153
|
+
else if (c in pair && open.pop() !== pair[c]) return false;
|
|
154
|
+
}
|
|
155
|
+
return open.length === 0 && !quote;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let cache: TemplateEntry[] | undefined;
|
|
159
|
+
|
|
160
|
+
/** The templates chant ships, checked. */
|
|
161
|
+
export function investigationTemplates(): TemplateEntry[] {
|
|
162
|
+
cache ??= parseTemplates(data);
|
|
163
|
+
return cache;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** A value spliced into a path pattern is one search term: letters, digits and `_ : . - / * ( ) , [ ]` only. */
|
|
167
|
+
const TERM_VALUE = /^[A-Za-z0-9_:.\-/*(),[\]]+$/;
|
|
168
|
+
|
|
169
|
+
export interface ExpandedTemplate {
|
|
170
|
+
template: string;
|
|
171
|
+
kind: TemplateQueryKind;
|
|
172
|
+
reads: TemplateReads;
|
|
173
|
+
/** path: the chant search query. cypher: the statement, unchanged. */
|
|
174
|
+
text: string;
|
|
175
|
+
/** cypher: the parameters, the resource as `key`. */
|
|
176
|
+
params?: Record<string, string>;
|
|
177
|
+
/** The scope inputs given, which say where the query is read. */
|
|
178
|
+
scope?: Record<string, string>;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Fill one template. `inputs` holds every input by name, the resource input
|
|
183
|
+
* as the resolved node's id (path) or key (Cypher). A value that would make
|
|
184
|
+
* a second search term is refused.
|
|
185
|
+
*/
|
|
186
|
+
export function expandTemplate(t: TemplateEntry, inputs: Record<string, string>): ExpandedTemplate {
|
|
187
|
+
for (const i of t.inputs) if (!inputs[i.name]) throw new TemplateCatalogError(`${t.id} needs ${i.name}: ${i.description}`);
|
|
188
|
+
for (const k of Object.keys(inputs)) if (!t.inputs.some((i) => i.name === k)) throw new TemplateCatalogError(`${t.id} takes no input ${k}`);
|
|
189
|
+
const scopes = t.inputs.filter((i) => i.kind === "scope");
|
|
190
|
+
const scope = scopes.length ? Object.fromEntries(scopes.map((i) => [i.name, inputs[i.name]!])) : undefined;
|
|
191
|
+
const head = { template: t.id, kind: t.query.kind, reads: t.reads, ...(scope ? { scope } : {}) };
|
|
192
|
+
if (t.query.kind === "cypher") {
|
|
193
|
+
const resource = t.inputs[0].name;
|
|
194
|
+
const params: Record<string, string> = { key: inputs[resource]! };
|
|
195
|
+
for (const i of t.inputs) if (i.name !== resource && i.kind !== "scope") params[i.name] = inputs[i.name]!;
|
|
196
|
+
return { ...head, text: t.query.text, params };
|
|
197
|
+
}
|
|
198
|
+
const text = t.query.text.replace(PLACEHOLDER, (_, name: string) => {
|
|
199
|
+
const v = inputs[name]!;
|
|
200
|
+
if (!TERM_VALUE.test(v)) throw new TemplateCatalogError(`${t.id}: ${name} ${JSON.stringify(v)} is not one search term`);
|
|
201
|
+
return v;
|
|
202
|
+
});
|
|
203
|
+
return { ...head, text };
|
|
204
|
+
}
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
* lexicon's `change-set` subpath must not reach `typescript`, esbuild, zod or
|
|
8
8
|
* chant's fold, lint and codegen modules. This bundles the three entry points
|
|
9
9
|
* the way terragucci would and reads esbuild's own list of what went in.
|
|
10
|
+
*
|
|
11
|
+
* The drift record (#4451) ships under the same rule: `@intentius/chant/
|
|
12
|
+
* drift-record` and the terraform lexicon's `drift-record` subpath bundle to
|
|
13
|
+
* the plan hashing code, the observation contract, the change-set graph's
|
|
14
|
+
* node rule, the instance rollup, the sensitive-key list and the plan projection, and nothing
|
|
15
|
+
* heavier.
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
import { join } from "node:path";
|
|
@@ -50,8 +56,85 @@ describe("the change-set bundle", () => {
|
|
|
50
56
|
"packages/core/src/intrinsic.ts",
|
|
51
57
|
"packages/core/src/lifecycle/plan-digest.ts",
|
|
52
58
|
"packages/core/src/plan-summary.ts",
|
|
59
|
+
// The next-release floor constant (#4480): no imports.
|
|
60
|
+
"packages/core/src/release-floors.ts",
|
|
53
61
|
]);
|
|
54
62
|
const bytes = result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0);
|
|
55
63
|
expect(bytes).toBeLessThan(BUDGET_BYTES);
|
|
56
64
|
});
|
|
65
|
+
|
|
66
|
+
test("the drift-record bundle holds the record, its adapters and the digest, and nothing heavier", async () => {
|
|
67
|
+
const result = await build({
|
|
68
|
+
entryPoints: [
|
|
69
|
+
join(REPO, "packages", "core", "src", "drift-record.ts"),
|
|
70
|
+
join(REPO, "lexicons", "terraform", "src", "drift-record.ts"),
|
|
71
|
+
],
|
|
72
|
+
bundle: true,
|
|
73
|
+
platform: "node",
|
|
74
|
+
format: "esm",
|
|
75
|
+
write: false,
|
|
76
|
+
metafile: true,
|
|
77
|
+
outdir: "out",
|
|
78
|
+
logLevel: "silent",
|
|
79
|
+
});
|
|
80
|
+
const inputs = Object.keys(result.metafile.inputs);
|
|
81
|
+
for (const pattern of FORBIDDEN) {
|
|
82
|
+
expect(inputs.filter((i) => pattern.test(i)), String(pattern)).toEqual([]);
|
|
83
|
+
}
|
|
84
|
+
expect(inputs.map((i) => i.replace(/^.*?(packages|lexicons)\//, "$1/")).sort()).toEqual([
|
|
85
|
+
"lexicons/terraform/src/drift-record.ts",
|
|
86
|
+
"lexicons/terraform/src/live-plan-reasons.ts",
|
|
87
|
+
"lexicons/terraform/src/plan-observation.ts",
|
|
88
|
+
"packages/core/src/change-set-graph.ts",
|
|
89
|
+
"packages/core/src/content-digest.ts",
|
|
90
|
+
"packages/core/src/declarable.ts",
|
|
91
|
+
"packages/core/src/drift-record.ts",
|
|
92
|
+
"packages/core/src/effect-receipt.ts",
|
|
93
|
+
"packages/core/src/instance-nodes.ts",
|
|
94
|
+
"packages/core/src/intrinsic.ts",
|
|
95
|
+
"packages/core/src/lifecycle/plan-digest.ts",
|
|
96
|
+
"packages/core/src/observation.ts",
|
|
97
|
+
"packages/core/src/pulumi-urn.ts",
|
|
98
|
+
"packages/core/src/sensitive-key.ts",
|
|
99
|
+
]);
|
|
100
|
+
// About 66 KB today: each entry point carries its own copy of the shared modules.
|
|
101
|
+
const bytes = result.outputFiles.reduce((n, f) => n + f.contents.byteLength, 0);
|
|
102
|
+
expect(bytes).toBeLessThan(96 * 1024);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("the pulumi lexicon's change-set and drift-record adapters (#4453) bundle the same way", async () => {
|
|
106
|
+
const result = await build({
|
|
107
|
+
entryPoints: [
|
|
108
|
+
join(REPO, "lexicons", "pulumi", "src", "change-set.ts"),
|
|
109
|
+
join(REPO, "lexicons", "pulumi", "src", "drift-record.ts"),
|
|
110
|
+
],
|
|
111
|
+
bundle: true,
|
|
112
|
+
platform: "node",
|
|
113
|
+
format: "esm",
|
|
114
|
+
write: false,
|
|
115
|
+
metafile: true,
|
|
116
|
+
outdir: "out",
|
|
117
|
+
logLevel: "silent",
|
|
118
|
+
});
|
|
119
|
+
const inputs = Object.keys(result.metafile.inputs);
|
|
120
|
+
for (const pattern of FORBIDDEN) {
|
|
121
|
+
expect(inputs.filter((i) => pattern.test(i)), String(pattern)).toEqual([]);
|
|
122
|
+
}
|
|
123
|
+
expect(inputs.map((i) => i.replace(/^.*?(packages|lexicons)\//, "$1/")).sort()).toEqual([
|
|
124
|
+
"lexicons/pulumi/src/change-set.ts",
|
|
125
|
+
"lexicons/pulumi/src/drift-record.ts",
|
|
126
|
+
"lexicons/pulumi/src/values.ts",
|
|
127
|
+
"packages/core/src/change-set-graph.ts",
|
|
128
|
+
"packages/core/src/content-digest.ts",
|
|
129
|
+
"packages/core/src/declarable.ts",
|
|
130
|
+
"packages/core/src/drift-record.ts",
|
|
131
|
+
"packages/core/src/effect-receipt.ts",
|
|
132
|
+
"packages/core/src/instance-nodes.ts",
|
|
133
|
+
"packages/core/src/intrinsic.ts",
|
|
134
|
+
"packages/core/src/lifecycle/plan-digest.ts",
|
|
135
|
+
"packages/core/src/observation.ts",
|
|
136
|
+
"packages/core/src/pulumi-urn.ts",
|
|
137
|
+
"packages/core/src/sensitive-key.ts",
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
57
140
|
});
|
package/src/change-set-graph.ts
CHANGED
|
@@ -18,11 +18,25 @@
|
|
|
18
18
|
* a delete of a block the source no longer declares, or a warden or sql
|
|
19
19
|
* change outside the graph.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* An instance of a `count` or `for_each` block gets its instance node
|
|
22
|
+
* (#4454): the block node's id followed by the instance key as the planner
|
|
23
|
+
* prints it, `<root>/module.app/terraform_data.instance[0]`, with
|
|
24
|
+
* `instanceOf` naming the block node. That holds whether or not the graph
|
|
25
|
+
* lists the instance node, since a graph read of the source lists only the
|
|
26
|
+
* instances it can count (`./instance-nodes.ts`). Under an expanded module
|
|
27
|
+
* call the module key sits on the call's segment (#4486):
|
|
28
|
+
* `module.app[0].aws_eip.pool[1]` is `<root>/module.app[0]/aws_eip.pool[1]`.
|
|
29
|
+
* {@link graphNodeFor} is the same rule for a single address, which a drift
|
|
30
|
+
* row uses.
|
|
31
|
+
*
|
|
32
|
+
* Like `./change-set.ts`, this imports nothing at run time but
|
|
33
|
+
* `./instance-nodes.ts` and `./pulumi-urn.ts`, which import nothing either,
|
|
34
|
+
* so it bundles into one file with the change-set code.
|
|
23
35
|
*/
|
|
24
36
|
|
|
25
37
|
import type { ChangeSetDocument, ChangeSetEntry } from "./change-set";
|
|
38
|
+
import { terraformInstancePath } from "./instance-nodes";
|
|
39
|
+
import { pulumiNodeCandidates } from "./pulumi-urn";
|
|
26
40
|
|
|
27
41
|
/** The graph a document is mapped onto: the node ids of one member's own IR. */
|
|
28
42
|
export interface ChangeSetGraphNodes {
|
|
@@ -104,9 +118,14 @@ export function terraformBlockKey(address: string): string | undefined {
|
|
|
104
118
|
* - `terraform`, `tofu`, `choudoufu`: `<root>/<block key>`, the root being
|
|
105
119
|
* the change-set member (`terraformBlockKey`), then the address.
|
|
106
120
|
* - `warden`, `sql`: the address.
|
|
121
|
+
* - `pulumi`: the address is a URN, and the id is `<member>/<key>` by the
|
|
122
|
+
* one URN rule (`pulumiNodeCandidates`, `./pulumi-urn.ts`, #4456); none
|
|
123
|
+
* for a provider or the root stack.
|
|
107
124
|
*/
|
|
108
125
|
export function changeSetNodeCandidates(entry: Pick<ChangeSetEntry, "member" | "planner" | "address">): string[] {
|
|
109
126
|
switch (entry.planner) {
|
|
127
|
+
case "pulumi":
|
|
128
|
+
return pulumiNodeCandidates(entry);
|
|
110
129
|
case "terraform":
|
|
111
130
|
case "tofu":
|
|
112
131
|
case "choudoufu": {
|
|
@@ -118,24 +137,76 @@ export function changeSetNodeCandidates(entry: Pick<ChangeSetEntry, "member" | "
|
|
|
118
137
|
}
|
|
119
138
|
}
|
|
120
139
|
|
|
140
|
+
/** Where one address sits on a graph: its node, and the block node when that node is an instance's. */
|
|
141
|
+
export interface GraphNodePlacement {
|
|
142
|
+
/** The node id, with the member in front inside a workspace, or null when the graph holds none for the address. */
|
|
143
|
+
node: string | null;
|
|
144
|
+
/** The block node's id, set when `node` is an instance node (#4454). */
|
|
145
|
+
instanceOf?: string;
|
|
146
|
+
/** The source file of the node, or of its block node for an instance, from the graph's `sources`. */
|
|
147
|
+
source?: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The node an address of a member's planner is on, in `ids` (one member's
|
|
152
|
+
* node ids, without the member) with `prefix` in front: the first candidate
|
|
153
|
+
* ({@link changeSetNodeCandidates}) the graph holds, and for an instance of a
|
|
154
|
+
* terraform-family block that the graph holds, the instance node (#4454).
|
|
155
|
+
*/
|
|
156
|
+
function placeOn(
|
|
157
|
+
entry: Pick<ChangeSetEntry, "member" | "planner" | "address">,
|
|
158
|
+
ids: ReadonlySet<string>,
|
|
159
|
+
prefix: string,
|
|
160
|
+
sources: ReadonlyMap<string, string> | undefined,
|
|
161
|
+
): GraphNodePlacement {
|
|
162
|
+
const id = changeSetNodeCandidates(entry).find((c) => ids.has(c));
|
|
163
|
+
if (id === undefined) return { node: null };
|
|
164
|
+
|
|
165
|
+
const path =
|
|
166
|
+
(entry.planner === "terraform" || entry.planner === "tofu" || entry.planner === "choudoufu") && id !== entry.address
|
|
167
|
+
? terraformInstancePath(entry.address)
|
|
168
|
+
: undefined;
|
|
169
|
+
// The block node the graph holds is `<root>/<block>`; its instance is
|
|
170
|
+
// `<root>/<instance>`, the module key on its call segment (#4486).
|
|
171
|
+
const root = path !== undefined && id.endsWith(`/${path.block}`) ? id.slice(0, id.length - path.block.length) : undefined;
|
|
172
|
+
if (path === undefined || root === undefined) {
|
|
173
|
+
const source = sources?.get(id);
|
|
174
|
+
return { node: `${prefix}${id}`, ...(source !== undefined ? { source } : {}) };
|
|
175
|
+
}
|
|
176
|
+
const instance = `${root}${path.instance}`;
|
|
177
|
+
const source = sources?.get(instance) ?? sources?.get(id);
|
|
178
|
+
return { node: `${prefix}${instance}`, instanceOf: `${prefix}${id}`, ...(source !== undefined ? { source } : {}) };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The node one engine address is on in `graph`, by the change set's mapping
|
|
183
|
+
* rules: what a drift row's `node` and `instanceOf` are (#4454), so a drift
|
|
184
|
+
* row and a change-set entry for the same instance name the same node. A null
|
|
185
|
+
* `graph` places nothing.
|
|
186
|
+
*/
|
|
187
|
+
export function graphNodeFor(
|
|
188
|
+
entry: Pick<ChangeSetEntry, "member" | "planner" | "address">,
|
|
189
|
+
graph: ChangeSetGraphNodes | null,
|
|
190
|
+
): GraphNodePlacement {
|
|
191
|
+
if (graph === null) return { node: null };
|
|
192
|
+
return placeOn(entry, new Set(graph.ids), graph.member ? `${graph.member}/` : "", graph.sources);
|
|
193
|
+
}
|
|
194
|
+
|
|
121
195
|
/**
|
|
122
196
|
* The document with each entry's `node` set from `graph`, and `graph`
|
|
123
197
|
* recording which graph it was. A null `graph` (none could be read) gives
|
|
124
|
-
* every entry `node: null`. The digest is unchanged: `node`
|
|
198
|
+
* every entry `node: null`. The digest is unchanged: `node`, `instanceOf` and
|
|
199
|
+
* `source` are not hashed.
|
|
125
200
|
*/
|
|
126
201
|
export function mapChangeSetToGraph(doc: ChangeSetDocument, graph: ChangeSetGraphNodes | null): ChangeSetDocument {
|
|
127
202
|
if (graph === null) {
|
|
128
|
-
return { ...doc, entries: doc.entries.map((e) => ({ ...e, node: null })), graph: null };
|
|
203
|
+
return { ...doc, entries: doc.entries.map(({ instanceOf: _i, ...e }) => ({ ...e, node: null })), graph: null };
|
|
129
204
|
}
|
|
130
205
|
const ids = new Set(graph.ids);
|
|
131
206
|
const prefix = graph.member ? `${graph.member}/` : "";
|
|
132
207
|
return {
|
|
133
208
|
...doc,
|
|
134
|
-
entries: doc.entries.map((e) => {
|
|
135
|
-
const id = changeSetNodeCandidates(e).find((c) => ids.has(c));
|
|
136
|
-
const source = id === undefined ? undefined : graph.sources?.get(id);
|
|
137
|
-
return { ...e, node: id === undefined ? null : `${prefix}${id}`, ...(source !== undefined ? { source } : {}) };
|
|
138
|
-
}),
|
|
209
|
+
entries: doc.entries.map(({ instanceOf: _i, ...e }) => ({ ...e, ...placeOn(e, ids, prefix, graph.sources) })),
|
|
139
210
|
graph: { member: graph.member, at: graph.at },
|
|
140
211
|
};
|
|
141
212
|
}
|
package/src/change-set.test.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
changeSetDigest,
|
|
29
29
|
changeSetDocumentDigest,
|
|
30
30
|
composeChangeSet,
|
|
31
|
+
isEmptyChangeSet,
|
|
31
32
|
lifecyclePlanPart,
|
|
32
33
|
readChangeSetPartFile,
|
|
33
34
|
reconcilePlanPart,
|
|
@@ -291,3 +292,26 @@ describe("readChangeSetPartFile (#3767)", () => {
|
|
|
291
292
|
expect(failed.member).toMatchObject({ status: "failed", error: "out of order", planDigest: null });
|
|
292
293
|
});
|
|
293
294
|
});
|
|
295
|
+
|
|
296
|
+
describe("a grouped member (ws-107, #4440)", () => {
|
|
297
|
+
const digest = computePlanDigest("capability-group", { kind: "k", members: [], composition: "c" });
|
|
298
|
+
const part = (member: string, group?: { digest: string; reads: string[] }): ChangeSetPart => ({
|
|
299
|
+
member: { member, lexicon: "chant", planner: "chant", status: "planned", planDigest: computePlanDigest("p", member), holes: [], ...(group ? { group } : {}) },
|
|
300
|
+
entries: [],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("a member without a group hashes as before; with one, the group is in the digest and the schema", () => {
|
|
304
|
+
const plain = composeChangeSet([part("a"), part("b")]);
|
|
305
|
+
const grouped = composeChangeSet([part("a", { digest, reads: [] }), part("b", { digest, reads: ["a.id"] })]);
|
|
306
|
+
expect(plain.digest).toBe(changeSetDocumentDigest({ members: plain.members, entries: [] }));
|
|
307
|
+
expect(grouped.digest).not.toBe(plain.digest);
|
|
308
|
+
expect(composeChangeSet([part("a", { digest, reads: [] }), part("b", { digest, reads: [] })]).digest).not.toBe(grouped.digest);
|
|
309
|
+
expect(verifyChangeSetDigest(grouped)).toBe(true);
|
|
310
|
+
expectValid(grouped);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("a change set whose member reads a value its group computes is never empty (ruling 4)", () => {
|
|
314
|
+
expect(isEmptyChangeSet(composeChangeSet([part("a", { digest, reads: [] }), part("b", { digest, reads: [] })]))).toBe(true);
|
|
315
|
+
expect(isEmptyChangeSet(composeChangeSet([part("a", { digest, reads: [] }), part("b", { digest, reads: ["a.id"] })]))).toBe(false);
|
|
316
|
+
});
|
|
317
|
+
});
|
package/src/change-set.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* Each planner reaches it through an adapter that returns a
|
|
12
12
|
* {@link ChangeSetPart}: one member and its entries. The lifecycle and
|
|
13
13
|
* reconcile adapters are here; the terraform-family ones live in the
|
|
14
|
-
* terraform lexicon's `change-set` subpath
|
|
15
|
-
* lexicon's. A part another process wrote to a file reads back through
|
|
14
|
+
* terraform lexicon's `change-set` subpath, the SQL ones in the sql
|
|
15
|
+
* lexicon's and the Pulumi one in the pulumi lexicon's. A part another process wrote to a file reads back through
|
|
16
16
|
* {@link readChangeSetPartFile}. {@link composeChangeSet} joins
|
|
17
17
|
* the parts.
|
|
18
18
|
*
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
import { computePlanDigest } from "./lifecycle/plan-digest";
|
|
46
46
|
import type { ChangeSet as LifecycleChangeSet, ChangeSetEntry as LifecycleEntry } from "./lifecycle/change-set";
|
|
47
47
|
import type { ChangeSet as ReconcileChangeSet } from "./reconcile";
|
|
48
|
+
import { NEXT_RELEASE } from "./release-floors";
|
|
48
49
|
|
|
49
50
|
/** The schema a change-set document names in `$schema`. */
|
|
50
51
|
export const CHANGE_SET_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/change-set/v1/change-set.schema.json";
|
|
@@ -52,6 +53,12 @@ export const CHANGE_SET_SCHEMA_ID = "https://intentius.io/chant/schemas/workspac
|
|
|
52
53
|
/** The read-contract version the document follows. */
|
|
53
54
|
export const CHANGE_SET_CONTRACT = 1;
|
|
54
55
|
|
|
56
|
+
/** The first chant whose change set can carry a member's `group` (ws-107, #4440). */
|
|
57
|
+
export const CHANGE_SET_GROUP_FLOOR = NEXT_RELEASE;
|
|
58
|
+
|
|
59
|
+
/** The first chant whose change set can carry a member's `placeholders` (#4463). */
|
|
60
|
+
export const CHANGE_SET_PLACEHOLDERS_FLOOR = NEXT_RELEASE;
|
|
61
|
+
|
|
55
62
|
/** What a planner proposes for one resource. `no-op`, `read` and `forget` appear only when the planner reports them. */
|
|
56
63
|
export type ChangeSetAction = "create" | "update" | "replace" | "delete" | "read" | "no-op" | "forget";
|
|
57
64
|
|
|
@@ -61,16 +68,17 @@ export const CHANGE_SET_ACTIONS: readonly ChangeSetAction[] = ["create", "update
|
|
|
61
68
|
/** How much applying one change hurts, in the vocabulary of `./lifecycle/disruption.ts`. */
|
|
62
69
|
export type ChangeSetDisruption = "in-place" | "rolling" | "replace" | "destroy" | "unknown";
|
|
63
70
|
|
|
64
|
-
/** Which tool produced a member's plan. */
|
|
65
71
|
/**
|
|
66
72
|
* Which tool produced a member's plan. `sql` is a SQL schema change: a
|
|
67
73
|
* versioned plan (a product's migrations) or one of the sql lexicon's
|
|
68
74
|
* migration Ops' plans, through `@intentius/chant-lexicon-sql/change-set`.
|
|
75
|
+
* `pulumi` is a `pulumi preview --json` of a Pulumi member, through
|
|
76
|
+
* `@intentius/chant-lexicon-pulumi/change-set` (#4453); its addresses are URNs.
|
|
69
77
|
*/
|
|
70
|
-
export type ChangeSetPlanner = "terraform" | "tofu" | "choudoufu" | "chant" | "warden" | "sql";
|
|
78
|
+
export type ChangeSetPlanner = "terraform" | "tofu" | "choudoufu" | "chant" | "warden" | "sql" | "pulumi";
|
|
71
79
|
|
|
72
80
|
/** Every planner, in the order the schema lists them. */
|
|
73
|
-
export const CHANGE_SET_PLANNERS: readonly ChangeSetPlanner[] = ["terraform", "tofu", "choudoufu", "chant", "warden", "sql"];
|
|
81
|
+
export const CHANGE_SET_PLANNERS: readonly ChangeSetPlanner[] = ["terraform", "tofu", "choudoufu", "chant", "warden", "sql", "pulumi"];
|
|
74
82
|
|
|
75
83
|
/** One attribute a change writes, at top-level granularity. */
|
|
76
84
|
export interface ChangeSetAttribute {
|
|
@@ -109,6 +117,18 @@ export interface ChangeSetEntry {
|
|
|
109
117
|
importing?: true;
|
|
110
118
|
/** Set on update, replace and delete. `unknown` means nobody could say. */
|
|
111
119
|
disruption?: ChangeSetDisruption;
|
|
120
|
+
/**
|
|
121
|
+
* The planner could not enumerate what this entry stands for (#4453). A
|
|
122
|
+
* Pulumi program can register resources inside an `apply()`, which does not
|
|
123
|
+
* run at preview while its input is unknown, so the preview never lists
|
|
124
|
+
* them. The pulumi adapter keeps them in the set rather than dropping them:
|
|
125
|
+
* one entry per component whose children may appear, at
|
|
126
|
+
* `pulumiMayAppearAddress` of the component, and an entry for each resource
|
|
127
|
+
* the last export holds under such a component that the preview did not
|
|
128
|
+
* list or plans to delete. An entry carrying it is never empty
|
|
129
|
+
* ({@link isEmptyChangeSet}). Part of the digest.
|
|
130
|
+
*/
|
|
131
|
+
mayAppear?: true;
|
|
112
132
|
region?: string;
|
|
113
133
|
/** The estate, environment or org the change lands in. */
|
|
114
134
|
scope?: string;
|
|
@@ -120,8 +140,19 @@ export interface ChangeSetEntry {
|
|
|
120
140
|
* a delete of a block the source no longer declares. Absent until the
|
|
121
141
|
* document is mapped onto a graph (`./change-set-graph.ts`), and never
|
|
122
142
|
* part of the digest.
|
|
143
|
+
*
|
|
144
|
+
* An instance of a `count` or `for_each` block (#4454) gets the instance
|
|
145
|
+
* node's id, the block node's id followed by the key as the planner prints
|
|
146
|
+
* it (`<member>/aws_eip.pool[0]`), and {@link instanceOf} names the block
|
|
147
|
+
* node. See `./instance-nodes.ts`.
|
|
123
148
|
*/
|
|
124
149
|
node?: string | null;
|
|
150
|
+
/**
|
|
151
|
+
* On an entry whose `node` is an instance node (#4454): the id of the block
|
|
152
|
+
* node it is an instance of, the rollup, in the same id space as `node`.
|
|
153
|
+
* Absent on every other entry. Like `node`, never part of the digest.
|
|
154
|
+
*/
|
|
155
|
+
instanceOf?: string;
|
|
125
156
|
/**
|
|
126
157
|
* The source file that declares the entry's node, from the repository
|
|
127
158
|
* root (#4025): the node's `sourceLoc.file` in the member's graph IR, with
|
|
@@ -158,9 +189,36 @@ export interface ChangeSetMember {
|
|
|
158
189
|
* summary never folds it into a group of real plans.
|
|
159
190
|
*/
|
|
160
191
|
provisional?: true;
|
|
192
|
+
/**
|
|
193
|
+
* The inputs the member was previewed with stand-in values for (#4463),
|
|
194
|
+
* sorted: a Terraform root whose `vars` read an output not known yet is
|
|
195
|
+
* planned with a typed placeholder per such variable (`var.<name>`), and
|
|
196
|
+
* every attribute those drive is marked unknown in its entries. Its plan
|
|
197
|
+
* digest covers them. The apply plans it again with the real values and
|
|
198
|
+
* holds that plan to the preview's shape. Added within contract 1 by chant
|
|
199
|
+
* {@link CHANGE_SET_PLACEHOLDERS_FLOOR}.
|
|
200
|
+
*/
|
|
201
|
+
placeholders?: string[];
|
|
161
202
|
/** The planner's own digest of the same plan, when it prints one (choudoufu's per-root digest). */
|
|
162
203
|
nativeDigest?: string;
|
|
163
204
|
holes: ChangeSetHole[];
|
|
205
|
+
/**
|
|
206
|
+
* Set on a member a capability planned with others as one group (ws-107,
|
|
207
|
+
* #4440). Part of the document's digest when present. Added within
|
|
208
|
+
* contract 1 by chant {@link CHANGE_SET_GROUP_FLOOR}.
|
|
209
|
+
*/
|
|
210
|
+
group?: ChangeSetMemberGroup;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** A grouped member's group (ws-107): the group digest folded into its `planDigest`, and what it reads inside the group. */
|
|
214
|
+
export interface ChangeSetMemberGroup {
|
|
215
|
+
digest: string;
|
|
216
|
+
/**
|
|
217
|
+
* The `<component>.<output>` values it reads from another member of its
|
|
218
|
+
* group that the plan does not know, sorted. The group's apply computes
|
|
219
|
+
* them. A change set with any is never empty ({@link isEmptyChangeSet}).
|
|
220
|
+
*/
|
|
221
|
+
reads: string[];
|
|
164
222
|
}
|
|
165
223
|
|
|
166
224
|
/** A provider-defined side effect an apply runs: a Terraform `action` block's invocation. */
|
|
@@ -288,7 +346,8 @@ export function changeSetDigest(members: ReadonlyArray<Pick<ChangeSetMember, "me
|
|
|
288
346
|
*
|
|
289
347
|
* A release gate's document (#4045) also hashes the `releaseDigest` and
|
|
290
348
|
* `environment` it was planned for. A document without them hashes as it
|
|
291
|
-
* always has, so no earlier digest moves.
|
|
349
|
+
* always has, so no earlier digest moves. A grouped member's `group`
|
|
350
|
+
* (ws-107) is hashed with it the same way: only when present.
|
|
292
351
|
*/
|
|
293
352
|
export function changeSetDocumentDigest(
|
|
294
353
|
doc: Pick<ChangeSetDocument, "members" | "entries" | "sideEffects" | "releaseDigest" | "environment">,
|
|
@@ -296,15 +355,15 @@ export function changeSetDocumentDigest(
|
|
|
296
355
|
const provisional = new Set(doc.members.filter((m) => m.provisional === true).map((m) => m.member));
|
|
297
356
|
const members = doc.members
|
|
298
357
|
.filter((m) => !provisional.has(m.member))
|
|
299
|
-
.map((m) => ({ member: m.member, status: m.status, holes: m.holes }))
|
|
358
|
+
.map((m) => ({ member: m.member, status: m.status, holes: m.holes, ...(m.group ? { group: { digest: m.group.digest, reads: [...m.group.reads].sort() } } : {}) }))
|
|
300
359
|
.sort((a, b) => byString(a.member, b.member));
|
|
301
|
-
// An entry's graph node (#4022)
|
|
302
|
-
// the graph the change is, not what the plan
|
|
303
|
-
// the digest: mapping a stored
|
|
360
|
+
// An entry's graph node (#4022), its block node (#4454) and its source
|
|
361
|
+
// file (#4025) say where on the graph the change is, not what the plan
|
|
362
|
+
// does, so they stay outside the digest: mapping a stored
|
|
304
363
|
// document onto a graph never moves the value its gate bound.
|
|
305
364
|
const entries = doc.entries
|
|
306
365
|
.filter((e) => !provisional.has(e.member))
|
|
307
|
-
.map(({ node: _node, source: _source, ...e }) => e)
|
|
366
|
+
.map(({ node: _node, instanceOf: _instanceOf, source: _source, ...e }) => e)
|
|
308
367
|
.sort((a, b) => byString(entryKey(a), entryKey(b)));
|
|
309
368
|
const sideEffects = (doc.sideEffects ?? []).filter((s) => !provisional.has(s.member)).sort(bySideEffect);
|
|
310
369
|
const planned =
|
|
@@ -413,15 +472,19 @@ export function verifyChangeSetDigest(
|
|
|
413
472
|
/**
|
|
414
473
|
* Whether a change set changes nothing (ws-104 question 6, #4045): it has at
|
|
415
474
|
* least one member, every member planned, none failed or provisional, no
|
|
416
|
-
* member has a hole, and every entry is a `no-op` or a `read
|
|
417
|
-
* gate over such a document passes
|
|
475
|
+
* member has a hole, and every entry is a `no-op` or a `read` that no
|
|
476
|
+
* planner marked `mayAppear`. A release gate over such a document passes
|
|
477
|
+
* without an approval. A member that reads a value its group computes in its
|
|
478
|
+
* apply (`group.reads`, ws-107 ruling 4) makes it not empty: what the plan
|
|
479
|
+
* shows may not be all the apply does.
|
|
418
480
|
*/
|
|
419
481
|
export function isEmptyChangeSet(doc: Pick<ChangeSetDocument, "members" | "entries">): boolean {
|
|
420
482
|
if (doc.members.length === 0) return false;
|
|
421
483
|
for (const m of doc.members) {
|
|
422
484
|
if (m.status !== "planned" || m.provisional === true || m.holes.length > 0) return false;
|
|
485
|
+
if (m.group && m.group.reads.length > 0) return false;
|
|
423
486
|
}
|
|
424
|
-
return doc.entries.every((e) => e.action === "no-op" || e.action === "read");
|
|
487
|
+
return doc.entries.every((e) => (e.action === "no-op" || e.action === "read") && e.mayAppear !== true);
|
|
425
488
|
}
|
|
426
489
|
|
|
427
490
|
// ── chant lifecycle plan ───────────────────────────────────────────────────────
|