@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
|
@@ -2,7 +2,7 @@ import { isAbsolute, relative, resolve } from "node:path";
|
|
|
2
2
|
import { discoverOps } from "../../op/discover";
|
|
3
3
|
import { discover } from "../../discovery/index";
|
|
4
4
|
import { partitionByLexicon, computeStackGraph, build } from "../../build";
|
|
5
|
-
import { applyLexiconOutputs, buildGraphIr, buildLiveGraphIr, collectUnobserved, overlayGraphs, sourceOverlayGraphs, GRAPH_IR_VERSION, type GraphIR, type IRPipeline, type LexiconWorkspaceOutputs, type LiveObservation } from "../../graph-ir";
|
|
5
|
+
import { applyLexiconOutputs, buildGraphIr, buildLiveGraphIr, collectUnobserved, overlayGraphs, sourceOverlayGraphs, withInstanceNodes, GRAPH_IR_VERSION, type GraphIR, type IRPipeline, type LexiconWorkspaceOutputs, type LiveObservation } from "../../graph-ir";
|
|
6
6
|
import { behaviourOverlay } from "../../behaviour-overlay";
|
|
7
7
|
import type { SerializerResult } from "../../serializer";
|
|
8
8
|
import { applyBehaviourOverlay, behaviourRequestFromIr } from "../../behaviour-graph";
|
|
@@ -10,6 +10,7 @@ import { buildDeclaredPerStack } from "../../graph-declared";
|
|
|
10
10
|
import { mergeProjectOps } from "../../graph-ops";
|
|
11
11
|
import { reconstructEdges, mergeCatalogs, containmentGroups, type ReferenceCatalog, type ContainmentPair } from "../../graph-refs";
|
|
12
12
|
import { carryAnnotations, observeResources } from "../../lifecycle/observe";
|
|
13
|
+
import type { RootRead } from "../../observation";
|
|
13
14
|
import { withLiveReadSession } from "../../live-read-session";
|
|
14
15
|
import { replaySnapshots, hasSnapshot } from "../../lifecycle/replay";
|
|
15
16
|
import { loadChantConfig, environmentNames, matchesDeclaredEnvironment, loadChantConfigUpward, type ChantConfig } from "../../config";
|
|
@@ -293,6 +294,8 @@ async function runGraphLive(
|
|
|
293
294
|
let observations: LiveObservation[];
|
|
294
295
|
// Facts about absent entities (#4372), carried onto the overlay's nodes.
|
|
295
296
|
let annotations: Record<string, Record<string, unknown>> | undefined;
|
|
297
|
+
// Which read answered each root (#4452), carried onto the IR as written.
|
|
298
|
+
let rootReads: Record<string, RootRead> | undefined;
|
|
296
299
|
// A replay is a graph of what was recorded, so it must not reach the provider
|
|
297
300
|
// at all — not for observation and not for enrichment. Anything that quietly
|
|
298
301
|
// called out would make `--at` a live read with an older label on it.
|
|
@@ -322,6 +325,7 @@ async function runGraphLive(
|
|
|
322
325
|
});
|
|
323
326
|
observations = observeResult.observations;
|
|
324
327
|
annotations = observeResult.annotations;
|
|
328
|
+
rootReads = observeResult.rootReads;
|
|
325
329
|
const { errors, warnings, notes = [] } = observeResult;
|
|
326
330
|
for (const e of errors) console.error(formatWarning({ message: e }));
|
|
327
331
|
// Unobserved entities (#1089) arrive as warnings — a node missing from the
|
|
@@ -383,7 +387,13 @@ async function runGraphLive(
|
|
|
383
387
|
let containment: ContainmentPair[] = [];
|
|
384
388
|
if (catalogs.length > 0) {
|
|
385
389
|
const reconstructed = reconstructEdges(ir.nodes, mergeCatalogs(catalogs));
|
|
386
|
-
|
|
390
|
+
// Merge, never replace, as `search --live` does (#1273). An observation
|
|
391
|
+
// can carry edges of its own (terraform's state dependencies, #4520's
|
|
392
|
+
// engine facts), and a catalog on one observing lexicon used to wipe every
|
|
393
|
+
// lexicon's: the reason terraform's catalog stayed off its plugin until
|
|
394
|
+
// #4535.
|
|
395
|
+
const seen = new Set(ir.edges.map((e) => `${e.from}|${e.to}|${e.viaAttr ?? ""}`));
|
|
396
|
+
ir = { ...ir, edges: [...ir.edges, ...reconstructed.edges.filter((e) => !seen.has(`${e.from}|${e.to}|${e.viaAttr ?? ""}`))] };
|
|
387
397
|
containment = reconstructed.containment;
|
|
388
398
|
}
|
|
389
399
|
|
|
@@ -427,7 +437,12 @@ async function runGraphLive(
|
|
|
427
437
|
console.error(formatWarning({ message: "overlay: a build root failed to render — showing the provisioned graph without the declared overlay" }));
|
|
428
438
|
} else {
|
|
429
439
|
await mergeGraphOps(declared.entities, declared.sourceFiles, projectPath);
|
|
430
|
-
|
|
440
|
+
// Instance nodes (#4454) on the declared side too, so a live instance
|
|
441
|
+
// meets its declared node and an unread one is painted alone.
|
|
442
|
+
const declaredIr = withInstanceNodes(
|
|
443
|
+
withLexiconEdges(buildGraphIr(declared.entities, projectPath), plugins, declared.entities),
|
|
444
|
+
declared.entities,
|
|
445
|
+
);
|
|
431
446
|
ir =
|
|
432
447
|
args.overlayAnchor === "live"
|
|
433
448
|
? overlayGraphs(ir, declaredIr, overlayOpts)
|
|
@@ -484,6 +499,11 @@ async function runGraphLive(
|
|
|
484
499
|
}
|
|
485
500
|
}
|
|
486
501
|
|
|
502
|
+
// The structured read report (#4452) goes on last, past the overlay, lens
|
|
503
|
+
// and detail passes that each build a fresh IR, so it is in every format's
|
|
504
|
+
// IR whatever those kept.
|
|
505
|
+
if (rootReads && Object.keys(rootReads).length > 0) ir = { ...ir, rootReads };
|
|
506
|
+
|
|
487
507
|
return emitIr(ir, ctx, format);
|
|
488
508
|
}
|
|
489
509
|
|
|
@@ -938,7 +958,9 @@ async function runGraphView(
|
|
|
938
958
|
|
|
939
959
|
// Build the base IR, focus with a lens (declarable-level, most precise), then
|
|
940
960
|
// apply the detail tier — so e.g. blast:<resource> works before any collapse.
|
|
941
|
-
|
|
961
|
+
// Instance nodes (#4454) go on here, not in `buildGraphIr`, so search and
|
|
962
|
+
// the other readers of the base IR still count one node per block.
|
|
963
|
+
let ir: GraphIR = withInstanceNodes(buildGraphIr(result.entities, projectPath), result.entities);
|
|
942
964
|
|
|
943
965
|
// The declared half of the epic's delta (#2355): this path predicts the
|
|
944
966
|
// file, the live path predicts the account. Same helper, so the two shapes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant index <substrate>` as a registry command (chant#4532).
|
|
3
|
+
*
|
|
4
|
+
* At the terminal, main() hands `index`'s raw argv straight to
|
|
5
|
+
* `runCloudIndex` (../../lifecycle/cloud-index.ts), because its flags are its
|
|
6
|
+
* own. This entry is for a caller that has already parsed a command line
|
|
7
|
+
* with core's parser (the ACP server asks the registry whether a word is a
|
|
8
|
+
* verb): it rebuilds the words that parser keeps (the substrate, `--json`,
|
|
9
|
+
* `--help`), and refuses when the parsed line carried an action flag core's
|
|
10
|
+
* parser knows, so the read-only rule holds on this path too.
|
|
11
|
+
*/
|
|
12
|
+
import type { CommandContext } from "../registry";
|
|
13
|
+
|
|
14
|
+
export async function runIndexCommand(ctx: CommandContext): Promise<number> {
|
|
15
|
+
const { runCloudIndex } = await import("../../lifecycle/cloud-index");
|
|
16
|
+
const a = ctx.args;
|
|
17
|
+
const action = a.fix ? "--fix" : a.live ? "--live" : a.write ? "--write" : a.yes ? "--yes" : a.op ? "--op" : undefined;
|
|
18
|
+
const words = [...(a.positionals ?? (a.path && a.path !== "." ? [a.path] : []))];
|
|
19
|
+
if (words[0] === "diff") {
|
|
20
|
+
// #4533 — `chant index diff <a> [<b>]` reads two stored versions; it takes no action flag either.
|
|
21
|
+
if (action) {
|
|
22
|
+
process.stderr.write(`chant index diff: refused ${action}: the diff only reads the store\n`);
|
|
23
|
+
return 2;
|
|
24
|
+
}
|
|
25
|
+
const { runIndexDiff } = await import("./index-diff");
|
|
26
|
+
return runIndexDiff([...words.slice(1), ...(a.json ? ["--json"] : []), ...(a.help ? ["--help"] : [])]);
|
|
27
|
+
}
|
|
28
|
+
if (action) words.push(action);
|
|
29
|
+
if (a.json) words.push("--json");
|
|
30
|
+
if (a.help) words.push("--help");
|
|
31
|
+
return runCloudIndex(words);
|
|
32
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// #4533: `chant index diff <a> [<b>]` over a local store holding the two fixture versions of
|
|
2
|
+
// ../../lifecycle/__fixtures__/index-diff/, read through --store local:<dir> as chant index writes it.
|
|
3
|
+
import { copyFileSync, mkdirSync, mkdtempSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { runIndexDiff } from "./index-diff";
|
|
8
|
+
|
|
9
|
+
const FIXTURES = join(import.meta.dirname, "..", "..", "lifecycle", "__fixtures__", "index-diff");
|
|
10
|
+
const V1 = "20261026T120000Z-aaaaaaaaaaaa.json";
|
|
11
|
+
const V2 = "20261026T130000Z-bbbbbbbbbbbb.json";
|
|
12
|
+
const V3 = "20261026T140000Z-cccccccccccc.json";
|
|
13
|
+
const SCOPE = "cloud/aws/000000000000/5c1f0e2a9b7d";
|
|
14
|
+
const OTHER = "cloud/aws/000000000000/0123456789ab";
|
|
15
|
+
|
|
16
|
+
/** A scratch dir (no chant project) with a local store: SCOPE holds V1 and V2, OTHER holds V1's document as V3. */
|
|
17
|
+
function scratch() {
|
|
18
|
+
const cwd = mkdtempSync(join(tmpdir(), "chant-index-diff-"));
|
|
19
|
+
for (const [scope, file, as] of [[SCOPE, V1, V1], [SCOPE, V2, V2], [OTHER, V1, V3]] as const) {
|
|
20
|
+
const dir = join(cwd, "store", "index", ...scope.split("/"));
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
copyFileSync(join(FIXTURES, file), join(dir, as));
|
|
23
|
+
}
|
|
24
|
+
return cwd;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function run(argv: string[]) {
|
|
28
|
+
const cwd = scratch();
|
|
29
|
+
let out = "";
|
|
30
|
+
let err = "";
|
|
31
|
+
const code = await runIndexDiff([...argv, "--store", "local:store"], {
|
|
32
|
+
cwd,
|
|
33
|
+
env: { XDG_DATA_HOME: join(cwd, "none") },
|
|
34
|
+
stdout: (s) => void (out += s),
|
|
35
|
+
stderr: (s) => void (err += s),
|
|
36
|
+
});
|
|
37
|
+
return { code, out, err };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("chant index diff", () => {
|
|
41
|
+
it("prints the diff of two named versions as JSON", async () => {
|
|
42
|
+
const r = await run([`${SCOPE}/${V1}`, V2, "--json"]);
|
|
43
|
+
expect(r.code).toBe(0);
|
|
44
|
+
expect(JSON.parse(r.out)).toMatchObject({ diff: "v1", scopeKey: SCOPE, from: { version: V1 }, to: { version: V2 }, counts: { properties: 1, added: 1, removed: 1 } });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("a scope alone diffs its last two versions, as text", async () => {
|
|
48
|
+
const r = await run([SCOPE]);
|
|
49
|
+
expect(r.code).toBe(0);
|
|
50
|
+
expect(r.out).toContain(`Index diff: ${SCOPE}`);
|
|
51
|
+
expect(r.out).toContain('attrs.VersioningConfiguration.Status: "Suspended" -> "Enabled"');
|
|
52
|
+
expect(r.out).toContain("(not observed in the to read: read-failed, ListResources returned ThrottlingException)");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("refuses two scopes by name", async () => {
|
|
56
|
+
const r = await run([`${SCOPE}/${V1}`, `${OTHER}/${V3}`, "--json"]);
|
|
57
|
+
expect(r.code).toBe(1);
|
|
58
|
+
expect(JSON.parse(r.out)).toMatchObject({ error: "index-scope-mismatch", fromScopeKey: SCOPE, toScopeKey: OTHER });
|
|
59
|
+
expect(r.err).toContain("different scopes");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("usage errors: no version, an unknown flag", async () => {
|
|
63
|
+
expect((await run([])).code).toBe(2);
|
|
64
|
+
const r = await run([SCOPE, "--apply"]);
|
|
65
|
+
expect(r.code).toBe(2);
|
|
66
|
+
expect(r.err).toContain("unknown flag --apply");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant index diff <a> [<b>] [--store <spec>] [--json]` (#4533): what
|
|
3
|
+
* changed between two versions of one index scope.
|
|
4
|
+
*
|
|
5
|
+
* `<a>` is `<scopeKey>/<version>`, or `<scopeKey>` alone. `<b>` is
|
|
6
|
+
* `<scopeKey>/<version>`, or a version name in `<a>`'s scope. With no `<b>`,
|
|
7
|
+
* the version before `<a>` is diffed against `<a>`, and a scope alone diffs
|
|
8
|
+
* its last two versions.
|
|
9
|
+
*
|
|
10
|
+
* Versions are read through #4519's store API, from the store `chant index`
|
|
11
|
+
* writes to (#4532's {@link planIndexStore}: `--store <spec>`, else the
|
|
12
|
+
* project's store, else the per-user one), so a git-ref, local or s3 store
|
|
13
|
+
* diffs the same way. Nothing is built, no lexicon is loaded and no cloud is
|
|
14
|
+
* queried. The computation is ../../lifecycle/index-diff.ts.
|
|
15
|
+
*/
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { chantUserStoreDir, IndexUsageError, planIndexStore, type StorePlan } from "../../lifecycle/cloud-index";
|
|
18
|
+
import { diffIndexVersionRefs, IndexScopeMismatch, NotAnIndexVersion, parseIndexVersionRef, type IndexDiff } from "../../lifecycle/index-diff";
|
|
19
|
+
import { createSnapshotStore, resolveSnapshotStore, snapshotStoreConfig, type SnapshotStore } from "../../lifecycle/snapshot-store";
|
|
20
|
+
|
|
21
|
+
export const INDEX_DIFF_USAGE = `chant index diff <a> [<b>] [--store <spec>] [--json]
|
|
22
|
+
|
|
23
|
+
What changed between two versions of one index scope: nodes added and
|
|
24
|
+
removed, properties changed, edges added and removed. A node is removed only
|
|
25
|
+
where the later read listed its type and location; elsewhere it is not
|
|
26
|
+
observed. Two versions of different scopes are refused.
|
|
27
|
+
|
|
28
|
+
<a> <scopeKey>/<version>, or <scopeKey> for its latest version
|
|
29
|
+
<b> <scopeKey>/<version>, or a version name in <a>'s scope.
|
|
30
|
+
Absent: the version before <a> against <a> (a scope alone:
|
|
31
|
+
its last two versions)
|
|
32
|
+
--store <spec> the store to read, as chant index takes it (local,
|
|
33
|
+
local:<dir>, git-ref, git-ref:<ref>, s3,
|
|
34
|
+
s3://<bucket>[/<prefix>]). Default: the project's store,
|
|
35
|
+
else chant's per-user store
|
|
36
|
+
--json the diff as JSON
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
export interface IndexDiffRunOptions {
|
|
40
|
+
cwd?: string;
|
|
41
|
+
env?: Record<string, string | undefined>;
|
|
42
|
+
home?: string;
|
|
43
|
+
stdout?: (s: string) => void;
|
|
44
|
+
stderr?: (s: string) => void;
|
|
45
|
+
/** Open the planned store; tests pass a fake. */
|
|
46
|
+
openStore?: (plan: StorePlan, cwd: string) => Promise<Pick<SnapshotStore, "kind" | "listIndex" | "readIndexAt" | "fetch">>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function openReadStore(plan: StorePlan, cwd: string): Promise<SnapshotStore> {
|
|
50
|
+
switch (plan.kind) {
|
|
51
|
+
case "user":
|
|
52
|
+
return createSnapshotStore({ store: "local", dir: plan.dir }, { cwd });
|
|
53
|
+
case "project":
|
|
54
|
+
return resolveSnapshotStore({ cwd: plan.projectDir });
|
|
55
|
+
case "spec":
|
|
56
|
+
return createSnapshotStore(snapshotStoreConfig(plan.configured, plan.env), { cwd, env: plan.env });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const show = (v: unknown) => (v === undefined ? "(absent)" : JSON.stringify(v));
|
|
61
|
+
|
|
62
|
+
/** The diff as text, one line per change. */
|
|
63
|
+
export function renderIndexDiff(d: IndexDiff): string {
|
|
64
|
+
const lines: string[] = [];
|
|
65
|
+
lines.push(`Index diff: ${d.scopeKey}`);
|
|
66
|
+
lines.push(` from ${d.from.version ?? d.from.digest.slice(0, 12)} (observed ${d.from.observedAt})`);
|
|
67
|
+
lines.push(` to ${d.to.version ?? d.to.digest.slice(0, 12)} (observed ${d.to.observedAt})`);
|
|
68
|
+
if (d.unchanged) {
|
|
69
|
+
lines.push("", "Same digest: nothing changed.");
|
|
70
|
+
return `${lines.join("\n")}\n`;
|
|
71
|
+
}
|
|
72
|
+
const c = d.counts;
|
|
73
|
+
lines.push(
|
|
74
|
+
"",
|
|
75
|
+
`${c.added} added, ${c.removed} removed, ${c.changed} changed (${c.properties} properties), ${c.notObserved} not observed; edges ${c.edgesAdded} added, ${c.edgesRemoved} removed, ${c.edgesNotObserved} not observed`,
|
|
76
|
+
);
|
|
77
|
+
for (const n of d.nodes.added) lines.push(` + ${n.key}`);
|
|
78
|
+
for (const n of d.nodes.removed) lines.push(` - ${n.key}`);
|
|
79
|
+
for (const n of d.nodes.changed) {
|
|
80
|
+
lines.push(` ~ ${n.key}`);
|
|
81
|
+
for (const p of n.properties) lines.push(` ${p.path}: ${show(p.before)} -> ${show(p.after)}`);
|
|
82
|
+
}
|
|
83
|
+
for (const n of d.nodes.notObserved) lines.push(` ? ${n.key} (not observed in the ${n.side} read: ${n.verdict}${n.detail ? `, ${n.detail}` : ""})`);
|
|
84
|
+
for (const e of d.edges.added) lines.push(` + edge ${e.from} -> ${e.to}${e.viaAttr ? ` via ${e.viaAttr}` : ""}`);
|
|
85
|
+
for (const e of d.edges.removed) lines.push(` - edge ${e.from} -> ${e.to}${e.viaAttr ? ` via ${e.viaAttr}` : ""}`);
|
|
86
|
+
for (const e of d.edges.notObserved) lines.push(` ? edge ${e.from} -> ${e.to} (${e.unobservedEnd} not observed in the ${e.side} read: ${e.verdict})`);
|
|
87
|
+
for (const v of d.coverage) lines.push(` coverage ${v.listedType} @ ${v.location}: ${v.before ?? "(none)"} -> ${v.after ?? "(none)"}`);
|
|
88
|
+
return `${lines.join("\n")}\n`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `chant index diff`'s argv (the words after `diff`). Exit 0 on a diff, 1 on a refusal, 2 on a usage error. */
|
|
92
|
+
export async function runIndexDiff(argv: readonly string[], o: IndexDiffRunOptions = {}): Promise<number> {
|
|
93
|
+
const out = o.stdout ?? ((s: string) => void process.stdout.write(s));
|
|
94
|
+
const err = o.stderr ?? ((s: string) => void process.stderr.write(s));
|
|
95
|
+
const cwd = o.cwd ?? process.cwd();
|
|
96
|
+
const env = o.env ?? process.env;
|
|
97
|
+
if (argv.includes("-h") || argv.includes("--help")) {
|
|
98
|
+
out(INDEX_DIFF_USAGE);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
const refs: string[] = [];
|
|
102
|
+
let store: string | undefined;
|
|
103
|
+
let json = false;
|
|
104
|
+
for (let i = 0; i < argv.length; i++) {
|
|
105
|
+
const a = argv[i]!;
|
|
106
|
+
if (a === "--json") json = true;
|
|
107
|
+
else if (a === "--store") {
|
|
108
|
+
store = argv[++i];
|
|
109
|
+
if (!store || store.startsWith("-")) {
|
|
110
|
+
err(`chant index diff: --store needs a store spec\n\n${INDEX_DIFF_USAGE}`);
|
|
111
|
+
return 2;
|
|
112
|
+
}
|
|
113
|
+
} else if (a.startsWith("--store=")) store = a.slice("--store=".length);
|
|
114
|
+
else if (a.startsWith("-")) {
|
|
115
|
+
err(`chant index diff: unknown flag ${a}\n\n${INDEX_DIFF_USAGE}`);
|
|
116
|
+
return 2;
|
|
117
|
+
} else refs.push(a);
|
|
118
|
+
}
|
|
119
|
+
if (refs.length < 1 || refs.length > 2) {
|
|
120
|
+
err(`chant index diff: name one or two index versions\n\n${INDEX_DIFF_USAGE}`);
|
|
121
|
+
return 2;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let d: IndexDiff;
|
|
125
|
+
try {
|
|
126
|
+
const from = parseIndexVersionRef(refs[0]!);
|
|
127
|
+
const to = refs[1] ? parseIndexVersionRef(refs[1], from.scopeKey) : undefined;
|
|
128
|
+
const plan = planIndexStore({ store, cwd, env, defaultStoreDir: () => chantUserStoreDir(env, o.home ?? homedir()), command: "chant index diff" });
|
|
129
|
+
const s = await (o.openStore ?? openReadStore)(plan, cwd);
|
|
130
|
+
await s.fetch();
|
|
131
|
+
d = await diffIndexVersionRefs(s, from, to);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
if (e instanceof IndexUsageError) {
|
|
134
|
+
err(`${e.message}\n`);
|
|
135
|
+
return 2;
|
|
136
|
+
}
|
|
137
|
+
if (e instanceof IndexScopeMismatch || e instanceof NotAnIndexVersion) {
|
|
138
|
+
if (json) out(`${JSON.stringify({ error: e.code, message: e.message, ...(e instanceof IndexScopeMismatch ? { fromScopeKey: e.fromScopeKey, toScopeKey: e.toScopeKey } : {}) }, null, 2)}\n`);
|
|
139
|
+
err(`chant index diff: ${e.message}\n`);
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
throw e;
|
|
143
|
+
}
|
|
144
|
+
out(json ? `${JSON.stringify(d, null, 2)}\n` : renderIndexDiff(d));
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4482 (B4 of #4477): a local `chant run` of a TerraformApplyOp lands on the
|
|
3
|
+
* Op run ledger of the member that declares the Op, and `chant ledger runs`
|
|
4
|
+
* names the member whose directory holds the root it applied in
|
|
5
|
+
* `root_member`. That is the read behold's member run card takes for a
|
|
6
|
+
* choudoufu member applied from a chant one.
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { afterAll, beforeEach, describe, expect, it } from "vitest";
|
|
13
|
+
import type { ActivityFn, ActivityProfile } from "../../op/activity-registry";
|
|
14
|
+
import { OpRunFailure, runOpLocally } from "../../op/local-executor";
|
|
15
|
+
import { withKnownOutputs } from "../../op/activity-failure";
|
|
16
|
+
import type { OpConfig } from "../../op/types";
|
|
17
|
+
import { clearMemberLedgerCache } from "../../lifecycle/member-ledger";
|
|
18
|
+
import { readLedgerTable } from "../../lifecycle/ledger-read";
|
|
19
|
+
import { queryLedgers } from "./query";
|
|
20
|
+
|
|
21
|
+
const scratch: string[] = [];
|
|
22
|
+
afterAll(() => {
|
|
23
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
24
|
+
});
|
|
25
|
+
beforeEach(() => clearMemberLedgerCache());
|
|
26
|
+
|
|
27
|
+
function git(args: string[], cwd: string): void {
|
|
28
|
+
const r = spawnSync("git", args, { cwd, encoding: "utf-8" });
|
|
29
|
+
if (r.status !== 0) throw new Error(`git ${args.join(" ")}: ${r.stderr}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function workspace(): string {
|
|
33
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-root-member-")));
|
|
34
|
+
scratch.push(root);
|
|
35
|
+
const files: Record<string, string> = {
|
|
36
|
+
"chant.workspace.json": JSON.stringify({
|
|
37
|
+
name: "estate",
|
|
38
|
+
schema: 1,
|
|
39
|
+
members: [
|
|
40
|
+
{ name: "app", dir: "app", kind: "chant" },
|
|
41
|
+
{ name: "aws", dir: "estates/aws", kind: "choudoufu" },
|
|
42
|
+
],
|
|
43
|
+
}),
|
|
44
|
+
"app/chant.config.json": JSON.stringify({ lexicons: ["terraform"], terraform: { roots: { aws: { dir: "../estates/aws" } } } }),
|
|
45
|
+
"estates/aws/main.tf": "",
|
|
46
|
+
};
|
|
47
|
+
for (const [path, text] of Object.entries(files)) {
|
|
48
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
49
|
+
writeFileSync(join(root, path), text);
|
|
50
|
+
}
|
|
51
|
+
git(["init", "-q", "-b", "main"], root);
|
|
52
|
+
git(["config", "user.email", "test@chant.dev"], root);
|
|
53
|
+
git(["config", "user.name", "Test"], root);
|
|
54
|
+
git(["add", "-A"], root);
|
|
55
|
+
git(["commit", "-q", "-m", "init"], root);
|
|
56
|
+
return root;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const PROFILES: Record<string, ActivityProfile> = { single: { timeout: "5m", retry: { maximumAttempts: 1 } } };
|
|
60
|
+
|
|
61
|
+
// The phases TerraformApplyOp builds with `gate: "never"`, with the Init step's #4482 outcome.
|
|
62
|
+
const config: OpConfig = {
|
|
63
|
+
name: "aws-apply",
|
|
64
|
+
overview: "",
|
|
65
|
+
labels: { Apply: "true", TerraformRoot: "aws" },
|
|
66
|
+
phases: [
|
|
67
|
+
{ name: "Init", steps: [{ kind: "activity", fn: "terraformInit", args: { root: "aws" }, outcomeAttribute: { name: "TerraformRootPath", from: "rootPath" } }] },
|
|
68
|
+
{ name: "Plan", steps: [{ kind: "activity", fn: "terraformPlan", args: { root: "aws" } }] },
|
|
69
|
+
{ name: "Apply", steps: [{ kind: "activity", fn: "terraformApply", args: { root: "aws" } }] },
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
describe("chant ledger runs: root_member (#4482)", () => {
|
|
74
|
+
it("lists a local TerraformApplyOp run under the member whose root it applied", async () => {
|
|
75
|
+
const root = workspace();
|
|
76
|
+
const activities = new Map<string, ActivityFn>([
|
|
77
|
+
// terraformInit's own result: the root's directory relative to the workspace.
|
|
78
|
+
["terraformInit", async () => ({ dir: join(root, "estates/aws"), rootPath: "estates/aws" })],
|
|
79
|
+
["terraformPlan", async () => ({ changed: true })],
|
|
80
|
+
["terraformApply", async () => ({ applied: true })],
|
|
81
|
+
]);
|
|
82
|
+
const result = await runOpLocally(config, activities, PROFILES, undefined, { ledger: { cwd: join(root, "app") } });
|
|
83
|
+
expect(result.status).toBe("ok");
|
|
84
|
+
|
|
85
|
+
const doc = await readLedgerTable("runs", () => queryLedgers(root), { where: ["root_member=aws"] });
|
|
86
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
87
|
+
expect(doc.rows).toHaveLength(1);
|
|
88
|
+
expect(doc.rows[0]).toMatchObject({
|
|
89
|
+
member: "app",
|
|
90
|
+
op: "aws-apply",
|
|
91
|
+
status: "ok",
|
|
92
|
+
root_member: "aws",
|
|
93
|
+
labels: { Apply: "true", TerraformRoot: "aws" },
|
|
94
|
+
outcomes: { TerraformRootPath: "estates/aws" },
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("leaves root_member null for a run with no root path", async () => {
|
|
99
|
+
const root = workspace();
|
|
100
|
+
const plain: OpConfig = { name: "plain", overview: "", phases: [{ name: "Only", steps: [{ kind: "activity", fn: "noop" }] }] };
|
|
101
|
+
await runOpLocally(plain, new Map<string, ActivityFn>([["noop", async () => undefined]]), PROFILES, undefined, { ledger: { cwd: join(root, "app") } });
|
|
102
|
+
const doc = await readLedgerTable("runs", () => queryLedgers(root), {});
|
|
103
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
104
|
+
expect(doc.rows.map((r) => [r.op, r.root_member])).toEqual([["plain", null]]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("names root_member on a failed run when Init knew the root before it failed (#4512)", async () => {
|
|
108
|
+
const root = workspace();
|
|
109
|
+
const activities = new Map<string, ActivityFn>([
|
|
110
|
+
// What terraformInit throws when `terraform init` itself fails: the root resolved first.
|
|
111
|
+
["terraformInit", async () => {
|
|
112
|
+
throw withKnownOutputs(new Error("terraform init: backend unreachable"), { dir: join(root, "estates/aws"), rootPath: "estates/aws" });
|
|
113
|
+
}],
|
|
114
|
+
["terraformPlan", async () => ({ changed: true })],
|
|
115
|
+
["terraformApply", async () => ({ applied: true })],
|
|
116
|
+
]);
|
|
117
|
+
const failure = await runOpLocally(config, activities, PROFILES, undefined, { ledger: { cwd: join(root, "app") } }).then(
|
|
118
|
+
() => undefined,
|
|
119
|
+
(err: unknown) => err,
|
|
120
|
+
);
|
|
121
|
+
expect(failure).toBeInstanceOf(OpRunFailure);
|
|
122
|
+
expect((failure as OpRunFailure).result.status).toBe("fail");
|
|
123
|
+
|
|
124
|
+
const doc = await readLedgerTable("runs", () => queryLedgers(root), { where: ["root_member=aws"] });
|
|
125
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
126
|
+
expect(doc.rows).toHaveLength(1);
|
|
127
|
+
expect(doc.rows[0]).toMatchObject({
|
|
128
|
+
member: "app",
|
|
129
|
+
op: "aws-apply",
|
|
130
|
+
status: "fail",
|
|
131
|
+
root_member: "aws",
|
|
132
|
+
labels: { Apply: "true", TerraformRoot: "aws" },
|
|
133
|
+
outcomes: { TerraformRootPath: "estates/aws" },
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("leaves root_member null, meaning unknown, when Init fails before it knows the root, with the declared root key in labels (#4508)", async () => {
|
|
138
|
+
const root = workspace();
|
|
139
|
+
const activities = new Map<string, ActivityFn>([
|
|
140
|
+
// Failing before the root resolved (a chant.config.ts that throws), so no known outputs.
|
|
141
|
+
["terraformInit", async () => {
|
|
142
|
+
throw new Error("chant.config.ts: evaluation failed");
|
|
143
|
+
}],
|
|
144
|
+
["terraformPlan", async () => ({ changed: true })],
|
|
145
|
+
["terraformApply", async () => ({ applied: true })],
|
|
146
|
+
]);
|
|
147
|
+
// A failed run throws OpRunFailure, carrying the result; the ledger row is written first.
|
|
148
|
+
const failure = await runOpLocally(config, activities, PROFILES, undefined, { ledger: { cwd: join(root, "app") } }).then(
|
|
149
|
+
() => undefined,
|
|
150
|
+
(err: unknown) => err,
|
|
151
|
+
);
|
|
152
|
+
expect(failure).toBeInstanceOf(OpRunFailure);
|
|
153
|
+
expect((failure as OpRunFailure).result.status).not.toBe("ok");
|
|
154
|
+
|
|
155
|
+
const doc = await readLedgerTable("runs", () => queryLedgers(root), {});
|
|
156
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
157
|
+
expect(doc.rows).toHaveLength(1);
|
|
158
|
+
expect(doc.rows[0]).toMatchObject({ member: "app", op: "aws-apply", root_member: null, labels: { Apply: "true", TerraformRoot: "aws" } });
|
|
159
|
+
expect(doc.rows[0].outcomes).not.toHaveProperty("TerraformRootPath");
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant lifecycle drift [--member <name>] [--read <read>] [--resource <addr>]`
|
|
3
|
+
* (behold#995): the project's drift, as each lexicon's own engine reads it,
|
|
4
|
+
* printed as the drift-record NDJSON stream (`../../drift-record.ts`).
|
|
5
|
+
*
|
|
6
|
+
* This is the member half of `chant workspace drift`. A `terraform` or
|
|
7
|
+
* `choudoufu` member has no chant config of its own, so the workspace writes
|
|
8
|
+
* it a reader project (`../../workspace/kind-readers.ts`) and runs this
|
|
9
|
+
* command there, under the member's own toolchain, the way `workspace graph`
|
|
10
|
+
* runs `chant graph`. Every lexicon with a `readDrift` is asked; the lines
|
|
11
|
+
* are printed in the order they came, one JSON object per line. chant core
|
|
12
|
+
* never imports a lexicon to read drift.
|
|
13
|
+
*
|
|
14
|
+
* `--member` names the workspace member the records are for; it defaults to
|
|
15
|
+
* the project directory's name. A lexicon that refuses the read (a read its
|
|
16
|
+
* engine has not got, or one resource on an engine that cannot read one on
|
|
17
|
+
* its own) makes the command print `{"refused": {code, message}}` as its
|
|
18
|
+
* only stdout line and exit 3, so the workspace command can name the
|
|
19
|
+
* refusal. No lexicon with a drift read is the refusal `no-drift-read`.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { basename, resolve } from "node:path";
|
|
23
|
+
import { loadChantConfig } from "../../config";
|
|
24
|
+
import { DRIFT_READS, encodeDriftLine, type DriftLine, type DriftRead, type ReadDriftRefusalCode } from "../../drift-record";
|
|
25
|
+
import { formatError } from "../format";
|
|
26
|
+
import type { CommandContext } from "../registry";
|
|
27
|
+
|
|
28
|
+
/** The exit status of a refused read. */
|
|
29
|
+
export const DRIFT_REFUSED_EXIT = 3;
|
|
30
|
+
|
|
31
|
+
const USAGE = "chant lifecycle drift [--member <name>] [--read <state|plan|refresh|live-read|ownership>] [--resource <addr>]";
|
|
32
|
+
|
|
33
|
+
function refuse(code: ReadDriftRefusalCode, message: string): number {
|
|
34
|
+
console.log(JSON.stringify({ refused: { code, message } }));
|
|
35
|
+
return DRIFT_REFUSED_EXIT;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function runLifecycleDrift(ctx: CommandContext): Promise<number> {
|
|
39
|
+
const { args, plugins } = ctx;
|
|
40
|
+
const read = args.driftRead;
|
|
41
|
+
if (read !== undefined && !(DRIFT_READS as readonly string[]).includes(read)) {
|
|
42
|
+
console.error(formatError({ message: `--read ${read} is not a drift read; the reads are ${DRIFT_READS.join(", ")}`, hint: USAGE }));
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
const projectRoot = resolve(".");
|
|
46
|
+
const { config } = await loadChantConfig(projectRoot);
|
|
47
|
+
const member = args.members?.[0] ?? basename(projectRoot);
|
|
48
|
+
const readers = plugins.filter((p) => typeof p.readDrift === "function");
|
|
49
|
+
if (readers.length === 0) {
|
|
50
|
+
return refuse("no-drift-read", `no lexicon this project declares (${plugins.map((p) => p.name).join(", ") || "none"}) has a drift read`);
|
|
51
|
+
}
|
|
52
|
+
const observedAt = new Date().toISOString();
|
|
53
|
+
const lines: DriftLine[] = [];
|
|
54
|
+
for (const plugin of readers) {
|
|
55
|
+
const answer = await plugin.readDrift!({
|
|
56
|
+
member,
|
|
57
|
+
projectRoot,
|
|
58
|
+
config: config as Record<string, unknown>,
|
|
59
|
+
...(read !== undefined ? { read: read as DriftRead } : {}),
|
|
60
|
+
...(args.resource !== undefined ? { resource: args.resource } : {}),
|
|
61
|
+
observedAt,
|
|
62
|
+
});
|
|
63
|
+
if ("refused" in answer) return refuse(answer.refused.code, `${plugin.name}: ${answer.refused.message}`);
|
|
64
|
+
lines.push(...answer.lines);
|
|
65
|
+
}
|
|
66
|
+
for (const line of lines) process.stdout.write(encodeDriftLine(line));
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
@@ -45,6 +45,27 @@ vi.mock("../../lifecycle/git", () => ({
|
|
|
45
45
|
readBlobFromPath: (...args: unknown[]) => readBlobFromPathMock(...args),
|
|
46
46
|
writeBlobToPath: (...args: unknown[]) => writeBlobToPathMock(...args),
|
|
47
47
|
}));
|
|
48
|
+
// Snapshots are read through the index store (#4519); this one hands each read to the mocks above.
|
|
49
|
+
vi.mock("../../lifecycle/snapshot-store", async () => {
|
|
50
|
+
const { fakeSnapshotStore } = await import("../../lifecycle/snapshot-store-fake");
|
|
51
|
+
const store = fakeSnapshotStore({
|
|
52
|
+
readLatest: async (environment, key) => (await readSnapshotMock(environment, key)) as string | null,
|
|
53
|
+
list: async (opts) => ((await listSnapshotsMock(opts)) as Array<{ commit: string; date: string; message: string }>).map((e) => ({ version: e.commit, date: e.date, message: e.message })),
|
|
54
|
+
fetch: async () => {
|
|
55
|
+
await fetchLifecycleMock();
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
resolveSnapshotStore: async () => store,
|
|
60
|
+
readEnvironmentSnapshotsFrom: async (_store: unknown, environment: string) => readEnvironmentSnapshotsMock(environment),
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
// The baseline is a file under .chant/ (#4519); the write is mocked so a test never writes into the checkout.
|
|
64
|
+
const saveObservationBaselineMock = vi.fn();
|
|
65
|
+
vi.mock("../../lifecycle/observation-baseline", async (importOriginal) => ({
|
|
66
|
+
...(await importOriginal<typeof import("../../lifecycle/observation-baseline")>()),
|
|
67
|
+
saveObservationBaseline: (...args: unknown[]) => saveObservationBaselineMock(...args),
|
|
68
|
+
}));
|
|
48
69
|
vi.mock("../../lifecycle/snapshot", () => ({
|
|
49
70
|
takeSnapshot: (...args: unknown[]) => takeSnapshotMock(...args),
|
|
50
71
|
}));
|
|
@@ -138,6 +159,8 @@ describe("runLifecycleDiff --live", () => {
|
|
|
138
159
|
readBlobFromPathMock.mockResolvedValue(null); // no accepted baseline recorded
|
|
139
160
|
writeBlobToPathMock.mockReset();
|
|
140
161
|
writeBlobToPathMock.mockResolvedValue("sha");
|
|
162
|
+
saveObservationBaselineMock.mockReset();
|
|
163
|
+
saveObservationBaselineMock.mockResolvedValue({ where: ".chant/observation-baseline/prod.json", published: "file" });
|
|
141
164
|
pushLifecycleMock.mockReset();
|
|
142
165
|
pushLifecycleMock.mockResolvedValue(true);
|
|
143
166
|
});
|
|
@@ -715,27 +738,26 @@ describe("runLifecycleDiff --live", () => {
|
|
|
715
738
|
expect(output).toContain("could not be observed — that part of the estate is unknown, not clean");
|
|
716
739
|
});
|
|
717
740
|
|
|
718
|
-
test("--update-baseline writes what was reported
|
|
741
|
+
test("--update-baseline writes what was reported to the baseline file", async () => {
|
|
719
742
|
await runDiff([withDeep()], { updateBaseline: true });
|
|
720
|
-
expect(
|
|
721
|
-
const [
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
lexicons: { aws: { bucket: { accepted: Array<{ path: string; value: unknown }> } } };
|
|
726
|
-
};
|
|
743
|
+
expect(saveObservationBaselineMock).toHaveBeenCalledTimes(1);
|
|
744
|
+
const [written] = saveObservationBaselineMock.mock.calls[0] as [
|
|
745
|
+
{ environment: string; lexicons: { aws: { bucket: { accepted: Array<{ path: string; value: unknown }> } } } },
|
|
746
|
+
];
|
|
747
|
+
expect(written.environment).toBe("prod");
|
|
727
748
|
// Only drift is accepted. A held field needs no acceptance: it was never
|
|
728
749
|
// reported as a deviation to begin with (#2160).
|
|
729
750
|
expect(written.lexicons.aws.bucket.accepted.map((a) => a.path)).toEqual(["Versioning"]);
|
|
730
|
-
|
|
731
|
-
expect(
|
|
751
|
+
// Never on the orphan branch again (#4519).
|
|
752
|
+
expect(writeBlobToPathMock).not.toHaveBeenCalled();
|
|
753
|
+
expect(stderrBuf.join("\n")).toContain("accepted 1 deviation(s) into .chant/observation-baseline/prod.json");
|
|
732
754
|
});
|
|
733
755
|
|
|
734
756
|
test("--update-baseline with nothing reported writes nothing", async () => {
|
|
735
757
|
await runDiff([
|
|
736
758
|
withDeep({ observeResourcesDeep: staticDeepObservation({}) }),
|
|
737
759
|
], { updateBaseline: true });
|
|
738
|
-
expect(
|
|
760
|
+
expect(saveObservationBaselineMock).not.toHaveBeenCalled();
|
|
739
761
|
expect(stderrBuf.join("\n")).toContain("nothing to accept");
|
|
740
762
|
});
|
|
741
763
|
});
|