@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
|
@@ -15,6 +15,19 @@ vi.mock("./git", () => ({
|
|
|
15
15
|
pushLifecycle: () => pushLifecycleMock(),
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
|
+
// Snapshots are written through the index store (#4519); this store hands each write to the mocks above.
|
|
19
|
+
vi.mock("./snapshot-store", async () => {
|
|
20
|
+
const { fakeSnapshotStore } = await import("./snapshot-store-fake");
|
|
21
|
+
const store = fakeSnapshotStore({
|
|
22
|
+
write: async (environment, key, json) => ({ written: true, version: (await writeSnapshotMock(environment, key, json)) as string }),
|
|
23
|
+
publish: async () => {
|
|
24
|
+
await pushLifecycleMock();
|
|
25
|
+
return { status: "pushed" };
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
return { resolveSnapshotStore: async () => store };
|
|
29
|
+
});
|
|
30
|
+
|
|
18
31
|
const { takeSnapshot } = await import("./snapshot");
|
|
19
32
|
|
|
20
33
|
function makeBuildResult(entitiesByLexicon: Record<string, string[]>): BuildResult {
|
|
@@ -10,7 +10,8 @@ import { observeDeep } from "./deep-observe";
|
|
|
10
10
|
import type { DeepResourceObservation } from "../deep-observation";
|
|
11
11
|
import { computeBuildDigest } from "./digest";
|
|
12
12
|
import { collectDependencies, collectAmbient } from "./observe";
|
|
13
|
-
import {
|
|
13
|
+
import { snapshotStorageKey, getHeadCommit } from "./git";
|
|
14
|
+
import { resolveSnapshotStore, type SnapshotStore, type StorePublish } from "./snapshot-store";
|
|
14
15
|
import { sortedJsonReplacer } from "../utils";
|
|
15
16
|
import { formatUnobserved, normalizeObservation, unobservedAll, type UnobservedEntity } from "../observation";
|
|
16
17
|
// One list of secret-bearing property names for both observation depths
|
|
@@ -72,9 +73,12 @@ function validateResources(
|
|
|
72
73
|
|
|
73
74
|
export interface TakeSnapshotResult {
|
|
74
75
|
snapshots: LifecycleSnapshot[];
|
|
76
|
+
/** The version the last write made: a commit on a git store, an object version on s3. Empty when nothing was written. */
|
|
75
77
|
commit: string;
|
|
76
78
|
warnings: string[];
|
|
77
79
|
errors: string[];
|
|
80
|
+
/** Where the snapshots went (`refs/chant/snapshots`, a directory, `s3://...`), and whether they were kept (#4519). */
|
|
81
|
+
store?: { kind: SnapshotStore["kind"]; where: string; written: boolean; publish?: StorePublish };
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
/**
|
|
@@ -93,6 +97,8 @@ export async function takeSnapshot(
|
|
|
93
97
|
/** Kinds the PROJECT manages, not just this stack — a region whose stack
|
|
94
98
|
* declares no security group still has a default one (#1278). */
|
|
95
99
|
ambientKinds?: string[];
|
|
100
|
+
/** The index store to write to (#4519). Default: the project's, from `snapshots` in chant.config.ts. */
|
|
101
|
+
store?: SnapshotStore;
|
|
96
102
|
},
|
|
97
103
|
): Promise<TakeSnapshotResult> {
|
|
98
104
|
const stack = opts?.stack;
|
|
@@ -285,21 +291,31 @@ export async function takeSnapshot(
|
|
|
285
291
|
}
|
|
286
292
|
}
|
|
287
293
|
|
|
288
|
-
// Write
|
|
294
|
+
// Write every successful snapshot through the index store (#4519). Masking
|
|
295
|
+
// already happened above, so the store never sees a secret.
|
|
296
|
+
const store = opts?.store ?? (await resolveSnapshotStore({ cwd: opts?.cwd }));
|
|
289
297
|
let commitSha = "";
|
|
298
|
+
let written = false;
|
|
290
299
|
for (const snapshot of snapshots) {
|
|
291
300
|
const json = JSON.stringify(snapshot, sortedJsonReplacer, 2);
|
|
292
|
-
|
|
301
|
+
const result = await store.write(
|
|
293
302
|
snapshot.environment,
|
|
294
303
|
snapshotStorageKey(snapshot.lexicon, snapshot.stack),
|
|
295
304
|
json,
|
|
296
|
-
opts,
|
|
305
|
+
opts?.cwd ? { cwd: opts.cwd } : undefined,
|
|
297
306
|
);
|
|
307
|
+
if (result.written) {
|
|
308
|
+
written = true;
|
|
309
|
+
commitSha = result.version ?? commitSha;
|
|
310
|
+
} else if (result.reason) {
|
|
311
|
+
warnings.push(`${snapshot.lexicon}: snapshot not kept: ${result.reason}`);
|
|
312
|
+
}
|
|
298
313
|
}
|
|
299
314
|
|
|
300
315
|
// Push to remote
|
|
301
|
-
|
|
302
|
-
|
|
316
|
+
const publish = written ? await store.publish() : undefined;
|
|
317
|
+
if (publish && (publish.status === "stale" || publish.status === "failed")) {
|
|
318
|
+
warnings.push(`snapshot kept locally in ${store.where}, not pushed: ${publish.detail ?? publish.status}`);
|
|
303
319
|
}
|
|
304
320
|
|
|
305
321
|
return {
|
|
@@ -307,5 +323,6 @@ export async function takeSnapshot(
|
|
|
307
323
|
commit: commitSha,
|
|
308
324
|
warnings,
|
|
309
325
|
errors,
|
|
326
|
+
store: { kind: store.kind, where: store.where, written, ...(publish ? { publish } : {}) },
|
|
310
327
|
};
|
|
311
328
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { MASKED } from "../deep-observation";
|
|
3
|
+
import { indexCoverageFor, validateIndexDocument } from "./index-document";
|
|
4
|
+
import { listedNodeId, typeListing, type ListingLexicon, type ListingResult } from "./type-listing";
|
|
5
|
+
|
|
6
|
+
// #4521: a listing (behold#1029's shape) typed into an index version (behold#1025).
|
|
7
|
+
const lexicons: ListingLexicon[] = [
|
|
8
|
+
{
|
|
9
|
+
name: "aws",
|
|
10
|
+
listedTypes: { substrate: "aws", kindFor: (t) => (t === "AWS::S3::Bucket" || t === "AWS::RDS::DBInstance" ? t : undefined) },
|
|
11
|
+
deepNormalizationHooks: {
|
|
12
|
+
// Stands in for a lexicon noise rule: an ARN is server-populated.
|
|
13
|
+
prune: (node) => node.side === "live" && node.key === "Arn",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "k8s",
|
|
18
|
+
listedTypes: {
|
|
19
|
+
substrate: "kubernetes",
|
|
20
|
+
kindFor: (t) => (t === "apps/v1/Deployment" ? "K8s::Apps::Deployment" : undefined),
|
|
21
|
+
observedTree: ({ apiVersion: _a, kind: _k, ...rest }) => rest,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function listing(overrides: Partial<ListingResult> = {}): ListingResult {
|
|
27
|
+
return {
|
|
28
|
+
listing: "v1",
|
|
29
|
+
source: { name: "behold-lister", version: "0.0.0-test" },
|
|
30
|
+
scope: { substrate: "aws", scopeId: "123456789012", locations: ["us-east-1"], types: "*" },
|
|
31
|
+
startedAt: "2026-10-26T09:14:03.120Z",
|
|
32
|
+
completedAt: "2026-10-26T09:14:09.000Z",
|
|
33
|
+
resources: [
|
|
34
|
+
{
|
|
35
|
+
listedType: "AWS::S3::Bucket",
|
|
36
|
+
location: "us-east-1",
|
|
37
|
+
nativeId: "assets-bucket",
|
|
38
|
+
cloudIds: [{ kind: "import-id", value: "assets-bucket" }],
|
|
39
|
+
tags: { team: "web", "api-token": "<masked>" },
|
|
40
|
+
properties: { BucketName: "assets-bucket", Arn: "arn:aws:s3:::assets-bucket", Tags: [{ Key: "team", Value: "web" }] },
|
|
41
|
+
observedAt: "2026-10-26T09:14:04.000Z",
|
|
42
|
+
facts: { cloudIds: [{ kind: "import-id", value: "assets-bucket" }], observedAt: "2026-10-26T09:14:04.000Z", markers: { owner: "web", encoding: "plain", source: "live" } },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
listedType: "AWS::Pipes::Pipe",
|
|
46
|
+
location: "us-east-1",
|
|
47
|
+
nativeId: "orders-pipe",
|
|
48
|
+
cloudIds: [{ kind: "import-id", value: "orders-pipe" }],
|
|
49
|
+
tags: {},
|
|
50
|
+
properties: { Name: "orders-pipe", SourceParameters: { ClientSecret: "plain-text-if-the-lister-missed-it" } },
|
|
51
|
+
observedAt: "2026-10-26T09:14:05.000Z",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
coverage: [
|
|
55
|
+
{ listedType: "AWS::S3::Bucket", location: "us-east-1", verdict: "listed" },
|
|
56
|
+
{ listedType: "AWS::Pipes::Pipe", location: "us-east-1", verdict: "listed" },
|
|
57
|
+
],
|
|
58
|
+
notes: [{ code: "default-types", message: "types * listed the default set" }],
|
|
59
|
+
...overrides,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe("typeListing", () => {
|
|
64
|
+
test("a mapped type becomes a typed node with its lexicon's normalization", () => {
|
|
65
|
+
const doc = typeListing(listing(), lexicons);
|
|
66
|
+
const bucket = doc.graph.nodes.find((n) => n.listedType === "AWS::S3::Bucket")!;
|
|
67
|
+
expect(bucket).toMatchObject({
|
|
68
|
+
id: "aws/123456789012/us-east-1/AWS::S3::Bucket/assets-bucket",
|
|
69
|
+
kind: "AWS::S3::Bucket",
|
|
70
|
+
lexicon: "aws",
|
|
71
|
+
location: "us-east-1",
|
|
72
|
+
nativeId: "assets-bucket",
|
|
73
|
+
physicalId: "assets-bucket",
|
|
74
|
+
});
|
|
75
|
+
expect(bucket.unmappedKind).toBeUndefined();
|
|
76
|
+
// The hook pruned Arn; the rest is key-sorted as the deep path leaves it.
|
|
77
|
+
expect(bucket.attrs).toEqual({ BucketName: "assets-bucket", Tags: [{ Key: "team", Value: "web" }] });
|
|
78
|
+
expect(bucket.facts).toEqual({
|
|
79
|
+
markers: { owner: "web", encoding: "plain", source: "live" },
|
|
80
|
+
cloudIds: [{ kind: "import-id", value: "assets-bucket" }],
|
|
81
|
+
observedAt: "2026-10-26T09:14:04.000Z",
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("an unmapped type is kept as an unknown-kind node, never dropped, and named in a note", () => {
|
|
86
|
+
const input = listing();
|
|
87
|
+
const doc = typeListing(input, lexicons);
|
|
88
|
+
// Every listed object is a node: the count is the guard against a drop.
|
|
89
|
+
expect(doc.graph.nodes).toHaveLength(input.resources.length);
|
|
90
|
+
const pipe = doc.graph.nodes.find((n) => n.listedType === "AWS::Pipes::Pipe");
|
|
91
|
+
expect(pipe).toBeDefined();
|
|
92
|
+
expect(pipe).toMatchObject({ kind: "AWS::Pipes::Pipe", lexicon: "aws", unmappedKind: true, nativeId: "orders-pipe" });
|
|
93
|
+
expect(doc.notes?.filter((n) => n.kind === "unmapped-type").map((n) => n.listedType)).toEqual(["AWS::Pipes::Pipe"]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("secrets are masked with the snapshot path's one list, on mapped and unmapped nodes and on tags", () => {
|
|
97
|
+
const doc = typeListing(listing(), lexicons);
|
|
98
|
+
const pipe = doc.graph.nodes.find((n) => n.listedType === "AWS::Pipes::Pipe")!;
|
|
99
|
+
expect((pipe.attrs.SourceParameters as Record<string, unknown>).ClientSecret).toBe(MASKED);
|
|
100
|
+
const bucket = doc.graph.nodes.find((n) => n.listedType === "AWS::S3::Bucket")!;
|
|
101
|
+
expect(bucket.tags).toEqual({ "api-token": MASKED, team: "web" });
|
|
102
|
+
expect(JSON.stringify(doc)).not.toContain("plain-text-if-the-lister-missed-it");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("a lexicon's observed-tree reshape runs before its hooks", () => {
|
|
106
|
+
const doc = typeListing(
|
|
107
|
+
listing({
|
|
108
|
+
scope: { substrate: "kubernetes", scopeId: "kind-dev", locations: "*", types: "*" },
|
|
109
|
+
resources: [
|
|
110
|
+
{
|
|
111
|
+
listedType: "apps/v1/Deployment",
|
|
112
|
+
location: "web",
|
|
113
|
+
nativeId: "web/frontend",
|
|
114
|
+
cloudIds: [{ kind: "k8s-uid", value: "0d6f..." }],
|
|
115
|
+
tags: {},
|
|
116
|
+
properties: { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "frontend", namespace: "web" }, spec: { replicas: 2 } },
|
|
117
|
+
observedAt: "2026-10-26T09:14:04.000Z",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
coverage: [{ listedType: "*", location: "*", verdict: "listed" }],
|
|
121
|
+
notes: [],
|
|
122
|
+
}),
|
|
123
|
+
lexicons,
|
|
124
|
+
);
|
|
125
|
+
const [deploy] = doc.graph.nodes;
|
|
126
|
+
expect(deploy.id).toBe("kubernetes/kind-dev/web/apps%2Fv1%2FDeployment/web/frontend");
|
|
127
|
+
expect(deploy.kind).toBe("K8s::Apps::Deployment");
|
|
128
|
+
expect(deploy.attrs).toEqual({ metadata: { name: "frontend", namespace: "web" }, spec: { replicas: 2 } });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("the document is stamped with the listing's scope and validates", () => {
|
|
132
|
+
const doc = typeListing(listing(), lexicons);
|
|
133
|
+
expect(() => validateIndexDocument(doc)).not.toThrow();
|
|
134
|
+
expect(doc.stamp.source).toEqual({ kind: "cloud", name: "behold-lister", version: "0.0.0-test" });
|
|
135
|
+
expect(doc.stamp.observedAt).toBe("2026-10-26T09:14:03.120Z");
|
|
136
|
+
expect(doc.stamp.scope).toEqual({ kind: "cloud", substrate: "aws", scopeId: "123456789012", locations: ["us-east-1"], types: "*" });
|
|
137
|
+
expect(doc.graph.edges).toEqual([]);
|
|
138
|
+
expect(doc.notes?.map((n) => n.kind)).toEqual(["default-types", "unmapped-type"]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("the same listing in another order gives the same document", () => {
|
|
142
|
+
const a = listing();
|
|
143
|
+
const b = listing({ resources: [...a.resources].reverse() });
|
|
144
|
+
expect(typeListing(b, [...lexicons].reverse())).toEqual(typeListing(a, lexicons));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("the node id matches behold's listedNodeId: % and / escaped in the first four parts, the native id raw", () => {
|
|
148
|
+
expect(
|
|
149
|
+
listedNodeId({ substrate: "azure", scopeId: "sub-1", location: "eastus", listedType: "microsoft.storage/storageaccounts", nativeId: "/subscriptions/sub-1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/st1" }),
|
|
150
|
+
).toBe("azure/sub-1/eastus/microsoft.storage%2Fstorageaccounts//subscriptions/sub-1/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/st1");
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("indexCoverageFor", () => {
|
|
155
|
+
test('"*" matches every type or location, and an exact entry wins', () => {
|
|
156
|
+
const coverage = [
|
|
157
|
+
{ listedType: "*", location: "*", verdict: "listed" as const },
|
|
158
|
+
{ listedType: "microsoft.web/sites", location: "*", verdict: "no-credentials" as const },
|
|
159
|
+
{ listedType: "microsoft.web/sites", location: "westeurope", verdict: "read-failed" as const },
|
|
160
|
+
];
|
|
161
|
+
expect(indexCoverageFor(coverage, "microsoft.storage/storageaccounts", "eastus")?.verdict).toBe("listed");
|
|
162
|
+
expect(indexCoverageFor(coverage, "microsoft.web/sites", "eastus")?.verdict).toBe("no-credentials");
|
|
163
|
+
expect(indexCoverageFor(coverage, "microsoft.web/sites", "westeurope")?.verdict).toBe("read-failed");
|
|
164
|
+
expect(indexCoverageFor([{ listedType: "AWS::S3::Bucket", location: "us-east-1", verdict: "listed" }], "AWS::S3::Bucket", "us-west-2")).toBeUndefined();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed nodes for indexed resources (#4521, tier 1 of INTENTIUS/behold#1025).
|
|
3
|
+
*
|
|
4
|
+
* behold#1029's cloud lister reads one account, subscription, project or
|
|
5
|
+
* cluster through that cloud's inventory API and returns a
|
|
6
|
+
* {@link ListingResult}. {@link typeListing} turns it into an
|
|
7
|
+
* {@link IndexDocument}: each listed object becomes an `IRNode` whose `kind`
|
|
8
|
+
* is the lexicon kind that describes it, with its properties normalized by
|
|
9
|
+
* that lexicon's own deep-observe hooks, so an indexed tree has the shape an
|
|
10
|
+
* observed one has and the capability catalog (#4375) applies to it.
|
|
11
|
+
*
|
|
12
|
+
* Pure: no I/O and no clock. The lexicons come in as data (each plugin's
|
|
13
|
+
* `listedTypes` and `deepNormalizationHooks`), so behold passes the plugins
|
|
14
|
+
* it already loads and a test passes a table.
|
|
15
|
+
*
|
|
16
|
+
* Rules:
|
|
17
|
+
* - a listed type no lexicon maps stays a node of unknown kind: `kind` is
|
|
18
|
+
* the listed type, `lexicon` the substrate, `unmappedKind: true`. It is
|
|
19
|
+
* never dropped, and a note names each such type once;
|
|
20
|
+
* - secret-bearing values are masked with the one list the snapshot path
|
|
21
|
+
* uses (`isSensitiveKey`, ../sensitive-key.ts). The lister masked already;
|
|
22
|
+
* masking again here means no listing reaches the index unmasked;
|
|
23
|
+
* - a node id is the path-style id ruled on behold#1025 (Q2):
|
|
24
|
+
* `<substrate>/<scopeId>/<location>/<listedType>/<nativeId>`.
|
|
25
|
+
*
|
|
26
|
+
* Edges are #4522's (./derive-edges.ts); this module leaves `edges` empty.
|
|
27
|
+
*/
|
|
28
|
+
import type { DeepNormalizationHooks } from "../deep-observation";
|
|
29
|
+
import { MASKED, isSensitiveKey, normalizeDeepProperties } from "../deep-observation";
|
|
30
|
+
import type { EngineFacts } from "../engine-facts";
|
|
31
|
+
import type { GraphIR, IRNode } from "../graph-ir";
|
|
32
|
+
import {
|
|
33
|
+
canonicalScope,
|
|
34
|
+
indexDocumentDigest,
|
|
35
|
+
indexScopeKey,
|
|
36
|
+
type CloudIndexScope,
|
|
37
|
+
type CoverageEntry,
|
|
38
|
+
type IndexDocument,
|
|
39
|
+
type IndexNote,
|
|
40
|
+
} from "./index-document";
|
|
41
|
+
|
|
42
|
+
/** The four substrates the lister reads, one per run (behold#1025 Q6). */
|
|
43
|
+
export type ListingSubstrate = CloudIndexScope["substrate"];
|
|
44
|
+
|
|
45
|
+
/** One run's scope, as the lister names it. `"*"` means every location or type. */
|
|
46
|
+
export interface CloudScope {
|
|
47
|
+
substrate: ListingSubstrate;
|
|
48
|
+
scopeId: string;
|
|
49
|
+
locations: readonly string[] | "*";
|
|
50
|
+
types: readonly string[] | "*";
|
|
51
|
+
tagFilter?: Readonly<Record<string, string>>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A cloud id, kinds as #4520 ruled them (`EngineFacts.cloudIds`). */
|
|
55
|
+
export interface ListedCloudId {
|
|
56
|
+
kind: "import-id" | "arn" | "provider-id" | "self-link" | "k8s-uid";
|
|
57
|
+
value: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The facts a cloud listing can supply: markers read off tags, cloud ids, when it was read. */
|
|
61
|
+
export interface ListedFacts {
|
|
62
|
+
markers?: EngineFacts["markers"];
|
|
63
|
+
cloudIds: ListedCloudId[];
|
|
64
|
+
observedAt: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** One listed object (behold#1025 `ListedResource`). */
|
|
68
|
+
export interface ListedResource {
|
|
69
|
+
/** The path-style id; recomputed by {@link listedNodeId}, so a listing may leave it out. */
|
|
70
|
+
id?: string;
|
|
71
|
+
listedType: string;
|
|
72
|
+
location: string;
|
|
73
|
+
nativeId: string;
|
|
74
|
+
cloudIds: ListedCloudId[];
|
|
75
|
+
tags: Record<string, string>;
|
|
76
|
+
/** As returned, unnormalized, already masked by the lister. */
|
|
77
|
+
properties: Record<string, unknown>;
|
|
78
|
+
observedAt: string;
|
|
79
|
+
facts?: ListedFacts;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A lister note: a type with no lister, a disabled API, a tag filter. */
|
|
83
|
+
export interface ListingNote {
|
|
84
|
+
code: string;
|
|
85
|
+
message: string;
|
|
86
|
+
listedType?: string;
|
|
87
|
+
location?: string;
|
|
88
|
+
command?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The lister's whole output for one run (behold#1025 `ListingResult`, behold#1029). */
|
|
92
|
+
export interface ListingResult {
|
|
93
|
+
listing: "v1";
|
|
94
|
+
source?: { name: string; version?: string };
|
|
95
|
+
scope: CloudScope;
|
|
96
|
+
startedAt: string;
|
|
97
|
+
completedAt: string;
|
|
98
|
+
resources: ListedResource[];
|
|
99
|
+
coverage: CoverageEntry[];
|
|
100
|
+
notes: ListingNote[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* How a lexicon reads one substrate's listed types (a plugin's
|
|
105
|
+
* `listedTypes`). Data plus two pure functions.
|
|
106
|
+
*/
|
|
107
|
+
export interface ListedTypeMapping {
|
|
108
|
+
substrate: ListingSubstrate;
|
|
109
|
+
/** The lexicon kind for a listed type, or undefined when the lexicon has none. */
|
|
110
|
+
kindFor(listedType: string): string | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* The listed properties reshaped into the tree this lexicon's deep reader
|
|
113
|
+
* returns, before the hooks run: Azure flattens `properties.*` beside
|
|
114
|
+
* `location` and `sku`, Kubernetes drops `apiVersion` and `kind`. Absent
|
|
115
|
+
* means the listing's shape already is that tree (Cloud Control's
|
|
116
|
+
* CloudFormation model).
|
|
117
|
+
*/
|
|
118
|
+
observedTree?(properties: Record<string, unknown>, context: { listedType: string; kind: string }): Record<string, unknown>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** What {@link typeListing} needs from a lexicon. A `LexiconPlugin` is one. */
|
|
122
|
+
export interface ListingLexicon {
|
|
123
|
+
name: string;
|
|
124
|
+
listedTypes?: ListedTypeMapping;
|
|
125
|
+
deepNormalizationHooks?: DeepNormalizationHooks;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function escapeSegment(s: string): string {
|
|
129
|
+
return s.replace(/%/g, "%25").replace(/\//g, "%2F");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `<substrate>/<scopeId>/<location>/<listedType>/<nativeId>` (behold#1025
|
|
134
|
+
* Q2), exactly as behold's lister writes it (src/cloud-lister/ids.ts): `%`
|
|
135
|
+
* and `/` are escaped in the first four parts, and the native id is last and
|
|
136
|
+
* unescaped, so everything after the fourth `/` is it.
|
|
137
|
+
*/
|
|
138
|
+
export function listedNodeId(parts: { substrate: string; scopeId: string; location: string; listedType: string; nativeId: string }): string {
|
|
139
|
+
return [
|
|
140
|
+
escapeSegment(parts.substrate),
|
|
141
|
+
escapeSegment(parts.scopeId),
|
|
142
|
+
escapeSegment(parts.location),
|
|
143
|
+
escapeSegment(parts.listedType),
|
|
144
|
+
parts.nativeId,
|
|
145
|
+
].join("/");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The lexicon (and kind) for one listed type: the first mapping, by lexicon name, that answers. */
|
|
149
|
+
export function resolveListedKind(
|
|
150
|
+
substrate: ListingSubstrate,
|
|
151
|
+
listedType: string,
|
|
152
|
+
lexicons: readonly ListingLexicon[],
|
|
153
|
+
): { lexicon: ListingLexicon; kind: string } | undefined {
|
|
154
|
+
for (const lexicon of lexicons) {
|
|
155
|
+
const mapping = lexicon.listedTypes;
|
|
156
|
+
if (!mapping || mapping.substrate !== substrate) continue;
|
|
157
|
+
const kind = mapping.kindFor(listedType);
|
|
158
|
+
if (kind !== undefined) return { lexicon, kind };
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function maskTags(tags: Record<string, string>): Record<string, string> {
|
|
164
|
+
const out: Record<string, string> = {};
|
|
165
|
+
for (const k of Object.keys(tags).sort()) out[k] = isSensitiveKey(k) ? MASKED : tags[k];
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const byName = (a: ListingLexicon, b: ListingLexicon) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
170
|
+
|
|
171
|
+
/** One listed object as a typed (or unknown-kind) node. */
|
|
172
|
+
export function typeListedResource(
|
|
173
|
+
scope: CloudScope,
|
|
174
|
+
resource: ListedResource,
|
|
175
|
+
lexicons: readonly ListingLexicon[],
|
|
176
|
+
): IRNode {
|
|
177
|
+
const sorted = [...lexicons].sort(byName);
|
|
178
|
+
const resolved = resolveListedKind(scope.substrate, resource.listedType, sorted);
|
|
179
|
+
const id = listedNodeId({
|
|
180
|
+
substrate: scope.substrate,
|
|
181
|
+
scopeId: scope.scopeId,
|
|
182
|
+
location: resource.location,
|
|
183
|
+
listedType: resource.listedType,
|
|
184
|
+
nativeId: resource.nativeId,
|
|
185
|
+
});
|
|
186
|
+
let kind: string;
|
|
187
|
+
let lexicon: string;
|
|
188
|
+
let attrs: Record<string, unknown>;
|
|
189
|
+
if (resolved) {
|
|
190
|
+
kind = resolved.kind;
|
|
191
|
+
lexicon = resolved.lexicon.name;
|
|
192
|
+
const reshape = resolved.lexicon.listedTypes?.observedTree;
|
|
193
|
+
const tree = reshape ? reshape(resource.properties, { listedType: resource.listedType, kind }) : resource.properties;
|
|
194
|
+
attrs = normalizeDeepProperties(tree, { entityType: kind, side: "live", hooks: resolved.lexicon.deepNormalizationHooks });
|
|
195
|
+
} else {
|
|
196
|
+
kind = resource.listedType;
|
|
197
|
+
lexicon = scope.substrate;
|
|
198
|
+
// No lexicon, so no hooks: the listing's properties as returned, key-sorted and masked.
|
|
199
|
+
attrs = normalizeDeepProperties(resource.properties, { entityType: resource.listedType, side: "live" });
|
|
200
|
+
}
|
|
201
|
+
const cloudIds = resource.cloudIds.length > 0 ? resource.cloudIds : resource.facts?.cloudIds ?? [];
|
|
202
|
+
const facts: EngineFacts = {
|
|
203
|
+
...(resource.facts?.markers ? { markers: resource.facts.markers } : {}),
|
|
204
|
+
...(cloudIds.length > 0 ? { cloudIds: cloudIds.map((c) => ({ kind: c.kind, value: c.value })) } : {}),
|
|
205
|
+
observedAt: resource.facts?.observedAt ?? resource.observedAt,
|
|
206
|
+
};
|
|
207
|
+
return {
|
|
208
|
+
id,
|
|
209
|
+
kind,
|
|
210
|
+
lexicon,
|
|
211
|
+
attrs,
|
|
212
|
+
physicalId: resource.nativeId,
|
|
213
|
+
facts,
|
|
214
|
+
listedType: resource.listedType,
|
|
215
|
+
location: resource.location,
|
|
216
|
+
nativeId: resource.nativeId,
|
|
217
|
+
tags: maskTags(resource.tags ?? {}),
|
|
218
|
+
...(resolved ? {} : { unmappedKind: true }),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** The listing's scope as the index stamp's cloud scope. */
|
|
223
|
+
export function listingIndexScope(scope: CloudScope): CloudIndexScope {
|
|
224
|
+
return canonicalScope({
|
|
225
|
+
kind: "cloud",
|
|
226
|
+
substrate: scope.substrate,
|
|
227
|
+
scopeId: scope.scopeId,
|
|
228
|
+
locations: scope.locations,
|
|
229
|
+
types: scope.types,
|
|
230
|
+
...(scope.tagFilter ? { tagFilter: scope.tagFilter } : {}),
|
|
231
|
+
}) as CloudIndexScope;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Every listed object as a node, sorted by id. */
|
|
235
|
+
export function typeListingNodes(listing: ListingResult, lexicons: readonly ListingLexicon[]): IRNode[] {
|
|
236
|
+
const nodes = listing.resources.map((r) => typeListedResource(listing.scope, r, lexicons));
|
|
237
|
+
nodes.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
238
|
+
return nodes;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const compareNotes = (a: IndexNote, b: IndexNote): number => {
|
|
242
|
+
const ka = `${a.kind}\u0000${a.listedType ?? ""}\u0000${a.location ?? ""}\u0000${a.message}`;
|
|
243
|
+
const kb = `${b.kind}\u0000${b.listedType ?? ""}\u0000${b.location ?? ""}\u0000${b.message}`;
|
|
244
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The listing as one index version (behold#1025 schema). `graph.edges` is
|
|
249
|
+
* empty; chant#4522's `deriveEdges` fills it. Coverage passes through as the
|
|
250
|
+
* lister wrote it, wildcards included. The lister's notes keep their code as
|
|
251
|
+
* `kind`, and each unmapped listed type gets one `unmapped-type` note.
|
|
252
|
+
*/
|
|
253
|
+
export function typeListing(listing: ListingResult, lexicons: readonly ListingLexicon[]): IndexDocument {
|
|
254
|
+
const nodes = typeListingNodes(listing, lexicons);
|
|
255
|
+
const graph: GraphIR = {
|
|
256
|
+
version: 1,
|
|
257
|
+
nodes,
|
|
258
|
+
edges: [],
|
|
259
|
+
groups: { byLexicon: groupByLexicon(nodes) },
|
|
260
|
+
};
|
|
261
|
+
const coverage = [...listing.coverage].map((c) => ({ ...c }));
|
|
262
|
+
const notes: IndexNote[] = listing.notes.map((n) => ({
|
|
263
|
+
kind: n.code,
|
|
264
|
+
message: n.message,
|
|
265
|
+
...(n.listedType !== undefined ? { listedType: n.listedType } : {}),
|
|
266
|
+
...(n.location !== undefined ? { location: n.location } : {}),
|
|
267
|
+
...(n.command !== undefined ? { command: n.command } : {}),
|
|
268
|
+
}));
|
|
269
|
+
const unmapped = [...new Set(nodes.filter((n) => n.unmappedKind).map((n) => n.listedType!))].sort();
|
|
270
|
+
for (const t of unmapped) {
|
|
271
|
+
notes.push({
|
|
272
|
+
kind: "unmapped-type",
|
|
273
|
+
listedType: t,
|
|
274
|
+
message: `no lexicon kind maps the listed type ${t}; its objects are kept as nodes of unknown kind, with the listing's properties masked`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
notes.sort(compareNotes);
|
|
278
|
+
return assembleIndexDocument(listing, graph, coverage, notes);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Stamp a graph, coverage and notes read from `listing` as one index version. */
|
|
282
|
+
export function assembleIndexDocument(
|
|
283
|
+
listing: ListingResult,
|
|
284
|
+
graph: GraphIR,
|
|
285
|
+
coverage: CoverageEntry[],
|
|
286
|
+
notes: IndexNote[],
|
|
287
|
+
): IndexDocument {
|
|
288
|
+
const scope = listingIndexScope(listing.scope);
|
|
289
|
+
return {
|
|
290
|
+
index: "v1",
|
|
291
|
+
stamp: {
|
|
292
|
+
source: { kind: "cloud", name: listing.source?.name ?? "behold-lister", ...(listing.source?.version ? { version: listing.source.version } : {}) },
|
|
293
|
+
scope,
|
|
294
|
+
scopeKey: indexScopeKey(scope),
|
|
295
|
+
observedAt: listing.startedAt,
|
|
296
|
+
completedAt: listing.completedAt,
|
|
297
|
+
digest: indexDocumentDigest({ graph, coverage }),
|
|
298
|
+
},
|
|
299
|
+
coverage,
|
|
300
|
+
graph,
|
|
301
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function groupByLexicon(nodes: readonly IRNode[]): Record<string, string[]> {
|
|
306
|
+
const out: Record<string, string[]> = {};
|
|
307
|
+
for (const n of nodes) (out[n.lexicon] ??= []).push(n.id);
|
|
308
|
+
return Object.fromEntries(Object.keys(out).sort().map((k) => [k, out[k].sort()]));
|
|
309
|
+
}
|
|
@@ -57,6 +57,21 @@ export interface ComponentCheckContext {
|
|
|
57
57
|
* `none-by-design`, i.e. flags nothing).
|
|
58
58
|
*/
|
|
59
59
|
rollbackPolicies?: ReadonlyMap<string, RollbackPolicy>;
|
|
60
|
+
/**
|
|
61
|
+
* The project's `terraform.roots` entries marked `signalOnly` (#4484),
|
|
62
|
+
* keyed by root name. Supplied by `chant lint` from the project's config
|
|
63
|
+
* (`signalOnlyRootsOf` in ./rules/comp/comp008-applies-signal-only-root.ts);
|
|
64
|
+
* undefined when none was resolved, in which case COMP008 flags nothing.
|
|
65
|
+
*/
|
|
66
|
+
signalOnlyRoots?: ReadonlyMap<string, SignalOnlyRoot>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** One `terraform.roots` entry marked `signalOnly` (#4490). */
|
|
70
|
+
export interface SignalOnlyRoot {
|
|
71
|
+
/** The root's `dir` as the config writes it. */
|
|
72
|
+
dir: string;
|
|
73
|
+
/** The workspace member whose directory holds the root, when the workspace reads. */
|
|
74
|
+
owner?: string;
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
/** A diagnostic from a component composition check. Shaped like `PostSynthDiagnostic`, plus a `file` so it can be merged into `LintDiagnostic`s and reported per-file like every other lint diagnostic. */
|
|
@@ -104,7 +119,7 @@ export interface ComponentCheck {
|
|
|
104
119
|
export async function runComponentChecks(
|
|
105
120
|
path: string,
|
|
106
121
|
checks: ComponentCheck[],
|
|
107
|
-
registryContext?: Pick<ComponentCheckContext, "knownKinds" | "rollbackPolicies">,
|
|
122
|
+
registryContext?: Pick<ComponentCheckContext, "knownKinds" | "rollbackPolicies" | "signalOnlyRoots">,
|
|
108
123
|
sandbox?: boolean,
|
|
109
124
|
buildParams?: BuildParamProvenance[],
|
|
110
125
|
): Promise<ComponentCheckDiagnostic[]> {
|
|
@@ -137,6 +152,7 @@ export async function runComponentChecks(
|
|
|
137
152
|
components,
|
|
138
153
|
knownKinds: registryContext?.knownKinds,
|
|
139
154
|
rollbackPolicies: registryContext?.rollbackPolicies,
|
|
155
|
+
signalOnlyRoots: registryContext?.signalOnlyRoots,
|
|
140
156
|
};
|
|
141
157
|
for (const check of checks) {
|
|
142
158
|
diagnostics.push(...check.check(ctx));
|
package/src/lint/config.test.ts
CHANGED
|
@@ -424,6 +424,7 @@ describe("loadConfig", () => {
|
|
|
424
424
|
COMP005: "error",
|
|
425
425
|
COMP006: "error",
|
|
426
426
|
COMP007: "warning",
|
|
427
|
+
COMP008: "warning",
|
|
427
428
|
});
|
|
428
429
|
});
|
|
429
430
|
|
|
@@ -474,6 +475,7 @@ describe("loadConfig", () => {
|
|
|
474
475
|
COMP005: "off",
|
|
475
476
|
COMP006: "off",
|
|
476
477
|
COMP007: "off",
|
|
478
|
+
COMP008: "warning",
|
|
477
479
|
});
|
|
478
480
|
});
|
|
479
481
|
|
|
@@ -511,6 +513,7 @@ describe("loadConfig", () => {
|
|
|
511
513
|
COMP005: "error",
|
|
512
514
|
COMP006: "error",
|
|
513
515
|
COMP007: "warning",
|
|
516
|
+
COMP008: "warning",
|
|
514
517
|
});
|
|
515
518
|
});
|
|
516
519
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Component } from "../../../../../../components/component";
|
|
2
|
+
import { phase } from "../../../../../../components/component";
|
|
3
|
+
|
|
4
|
+
/** COMP008 fail case: a terraform-apply step deploys "azure", which the test marks signalOnly. */
|
|
5
|
+
export const dns: Component = {
|
|
6
|
+
name: "dns",
|
|
7
|
+
archetype: "infra",
|
|
8
|
+
dependsOn: [],
|
|
9
|
+
deploy: [
|
|
10
|
+
phase("Apply", [
|
|
11
|
+
{ kind: "terraform-apply", root: "dns" },
|
|
12
|
+
{ kind: "terraform-apply", root: "azure" },
|
|
13
|
+
]),
|
|
14
|
+
],
|
|
15
|
+
};
|