@intentius/chant 0.124.51 → 0.124.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/discover.js +3 -1
- package/dist/behaviour.d.ts +1 -1
- package/dist/behaviour.d.ts.map +1 -1
- package/dist/capabilities/templates.d.ts +65 -0
- package/dist/capabilities/templates.d.ts.map +1 -0
- package/dist/capabilities/templates.js +193 -0
- package/dist/capabilities/templates.json +145 -0
- package/dist/change-set-graph.d.ts +35 -3
- package/dist/change-set-graph.d.ts.map +1 -1
- package/dist/change-set-graph.js +60 -9
- package/dist/change-set.d.ts +65 -7
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +23 -12
- package/dist/ci-identity.d.ts +7 -2
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +4 -1
- package/dist/cli/commands/import-index.d.ts +52 -0
- package/dist/cli/commands/import-index.d.ts.map +1 -0
- package/dist/cli/commands/import-index.js +226 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +4 -2
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/components.js +11 -2
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +16 -3
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +22 -4
- package/dist/cli/handlers/index-cloud.d.ts +14 -0
- package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
- package/dist/cli/handlers/index-cloud.js +22 -0
- package/dist/cli/handlers/index-diff.d.ts +18 -0
- package/dist/cli/handlers/index-diff.d.ts.map +1 -0
- package/dist/cli/handlers/index-diff.js +142 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-drift.js +64 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +188 -28
- package/dist/cli/handlers/misc.d.ts +9 -0
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +77 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +17 -3
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +89 -15
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +28 -4
- package/dist/cli/handlers/scenario.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.js +8 -7
- package/dist/cli/handlers/search.d.ts +34 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +216 -36
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +55 -1
- package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
- package/dist/cli/mcp/lifecycle-tools.js +3 -2
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.js +4 -3
- package/dist/cli/mcp/workspace-tools.js +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +55 -2
- package/dist/cli/registry.d.ts +19 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cloud-lister/aws.d.ts +35 -0
- package/dist/cloud-lister/aws.d.ts.map +1 -0
- package/dist/cloud-lister/aws.js +203 -0
- package/dist/cloud-lister/azure.d.ts +34 -0
- package/dist/cloud-lister/azure.d.ts.map +1 -0
- package/dist/cloud-lister/azure.js +129 -0
- package/dist/cloud-lister/common.d.ts +40 -0
- package/dist/cloud-lister/common.d.ts.map +1 -0
- package/dist/cloud-lister/common.js +83 -0
- package/dist/cloud-lister/credentials.d.ts +50 -0
- package/dist/cloud-lister/credentials.d.ts.map +1 -0
- package/dist/cloud-lister/credentials.js +86 -0
- package/dist/cloud-lister/fakes.d.ts +27 -0
- package/dist/cloud-lister/fakes.d.ts.map +1 -0
- package/dist/cloud-lister/fakes.js +50 -0
- package/dist/cloud-lister/gcp.d.ts +42 -0
- package/dist/cloud-lister/gcp.d.ts.map +1 -0
- package/dist/cloud-lister/gcp.js +143 -0
- package/dist/cloud-lister/guard.d.ts +49 -0
- package/dist/cloud-lister/guard.d.ts.map +1 -0
- package/dist/cloud-lister/guard.js +103 -0
- package/dist/cloud-lister/ids.d.ts +23 -0
- package/dist/cloud-lister/ids.d.ts.map +1 -0
- package/dist/cloud-lister/ids.js +42 -0
- package/dist/cloud-lister/index.d.ts +67 -0
- package/dist/cloud-lister/index.d.ts.map +1 -0
- package/dist/cloud-lister/index.js +102 -0
- package/dist/cloud-lister/kubernetes.d.ts +64 -0
- package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
- package/dist/cloud-lister/kubernetes.js +276 -0
- package/dist/cloud-lister/markers.d.ts +31 -0
- package/dist/cloud-lister/markers.d.ts.map +1 -0
- package/dist/cloud-lister/markers.js +73 -0
- package/dist/cloud-lister/mask.d.ts +31 -0
- package/dist/cloud-lister/mask.d.ts.map +1 -0
- package/dist/cloud-lister/mask.js +74 -0
- package/dist/cloud-lister/sigv4.d.ts +29 -0
- package/dist/cloud-lister/sigv4.d.ts.map +1 -0
- package/dist/cloud-lister/sigv4.js +54 -0
- package/dist/cloud-lister/transport.d.ts +55 -0
- package/dist/cloud-lister/transport.d.ts.map +1 -0
- package/dist/cloud-lister/transport.js +86 -0
- package/dist/cloud-lister/types.d.ts +121 -0
- package/dist/cloud-lister/types.d.ts.map +1 -0
- package/dist/cloud-lister/types.js +24 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +6 -0
- package/dist/components/capability-plugin.d.ts +9 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability.d.ts +224 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +21 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +58 -14
- package/dist/components/covered-round.d.ts +173 -0
- package/dist/components/covered-round.d.ts.map +1 -0
- package/dist/components/covered-round.js +200 -0
- package/dist/components/declared-reads.d.ts +38 -0
- package/dist/components/declared-reads.d.ts.map +1 -0
- package/dist/components/declared-reads.js +131 -0
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/deploy-units.js +4 -0
- package/dist/components/driver.d.ts +42 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +74 -3
- package/dist/components/fan-out-run.d.ts +23 -1
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +82 -14
- package/dist/components/fan-out-support.d.ts +6 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +4 -1
- package/dist/components/fan-out.d.ts +28 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/groups.d.ts +229 -0
- package/dist/components/groups.d.ts.map +1 -0
- package/dist/components/groups.js +760 -0
- package/dist/components/index.d.ts +7 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +5 -0
- package/dist/components/plan-shape-conformance.d.ts +48 -0
- package/dist/components/plan-shape-conformance.d.ts.map +1 -0
- package/dist/components/plan-shape-conformance.js +122 -0
- package/dist/components/pr-run.d.ts +62 -7
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +354 -53
- package/dist/components/promote-rounds.d.ts +9 -0
- package/dist/components/promote-rounds.d.ts.map +1 -1
- package/dist/components/promote-rounds.js +23 -2
- package/dist/components/promote.d.ts +17 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +36 -6
- package/dist/components/release-gate.d.ts +23 -1
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +97 -33
- package/dist/components/run-groups.d.ts +58 -0
- package/dist/components/run-groups.d.ts.map +1 -0
- package/dist/components/run-groups.js +78 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +6 -1
- package/dist/components/verbs/index.d.ts +1 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +1 -0
- package/dist/components/verbs/member-outputs.d.ts +187 -0
- package/dist/components/verbs/member-outputs.d.ts.map +1 -0
- package/dist/components/verbs/member-outputs.js +218 -0
- package/dist/components/wave-plan.d.ts +2 -0
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +4 -1
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -0
- package/dist/deep-observation.d.ts +1 -13
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/deep-observation.js +5 -22
- package/dist/drift-record.d.ts +542 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +886 -0
- package/dist/engine-facts.d.ts +144 -0
- package/dist/engine-facts.d.ts.map +1 -0
- package/dist/engine-facts.js +14 -0
- package/dist/graph-ir.d.ts +153 -2
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +224 -6
- package/dist/graph-refs.d.ts +145 -0
- package/dist/graph-refs.d.ts.map +1 -1
- package/dist/graph-refs.js +305 -0
- package/dist/graph-schema/fixtures/adopt.json +15 -0
- package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/dist/graph-schema/fixtures/index.json +68 -0
- package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/dist/graph-schema/fixtures.d.ts +23 -0
- package/dist/graph-schema/fixtures.d.ts.map +1 -0
- package/dist/graph-schema/fixtures.js +246 -0
- package/dist/graph-schema/generate.d.ts +15 -0
- package/dist/graph-schema/generate.d.ts.map +1 -0
- package/dist/graph-schema/generate.js +267 -0
- package/dist/graph-schema/graph.schema.json +1387 -0
- package/dist/health.schema.json +2 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/instance-nodes.d.ts +159 -0
- package/dist/instance-nodes.d.ts.map +1 -0
- package/dist/instance-nodes.js +231 -0
- package/dist/lexicon.d.ts +72 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/adoption-file.d.ts +56 -0
- package/dist/lifecycle/adoption-file.d.ts.map +1 -0
- package/dist/lifecycle/adoption-file.js +123 -0
- package/dist/lifecycle/cloud-index.d.ts +186 -0
- package/dist/lifecycle/cloud-index.d.ts.map +1 -0
- package/dist/lifecycle/cloud-index.js +618 -0
- package/dist/lifecycle/derive-edges.d.ts +59 -0
- package/dist/lifecycle/derive-edges.d.ts.map +1 -0
- package/dist/lifecycle/derive-edges.js +126 -0
- package/dist/lifecycle/git.d.ts +10 -9
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +7 -1
- package/dist/lifecycle/identity-match.d.ts +133 -0
- package/dist/lifecycle/identity-match.d.ts.map +1 -0
- package/dist/lifecycle/identity-match.js +147 -0
- package/dist/lifecycle/index-diff.d.ts +224 -0
- package/dist/lifecycle/index-diff.d.ts.map +1 -0
- package/dist/lifecycle/index-diff.js +327 -0
- package/dist/lifecycle/index-document.d.ts +130 -0
- package/dist/lifecycle/index-document.d.ts.map +1 -0
- package/dist/lifecycle/index-document.js +189 -0
- package/dist/lifecycle/index-drift.d.ts +121 -0
- package/dist/lifecycle/index-drift.d.ts.map +1 -0
- package/dist/lifecycle/index-drift.js +178 -0
- package/dist/lifecycle/index-import.d.ts +168 -0
- package/dist/lifecycle/index-import.d.ts.map +1 -0
- package/dist/lifecycle/index-import.js +515 -0
- package/dist/lifecycle/index-join.d.ts +129 -0
- package/dist/lifecycle/index-join.d.ts.map +1 -0
- package/dist/lifecycle/index-join.js +248 -0
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/observation-baseline.d.ts +61 -21
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/observation-baseline.js +144 -19
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +6 -1
- package/dist/lifecycle/query.d.ts +45 -4
- package/dist/lifecycle/query.d.ts.map +1 -1
- package/dist/lifecycle/query.js +89 -7
- package/dist/lifecycle/replay.d.ts +10 -3
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/replay.js +10 -6
- package/dist/lifecycle/scenario.d.ts +4 -3
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
- package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-fake.js +21 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-s3.js +254 -0
- package/dist/lifecycle/snapshot-store.d.ts +170 -0
- package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store.js +832 -0
- package/dist/lifecycle/snapshot.d.ts +11 -0
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +18 -5
- package/dist/lifecycle/type-listing.d.ts +148 -0
- package/dist/lifecycle/type-listing.d.ts.map +1 -0
- package/dist/lifecycle/type-listing.js +166 -0
- package/dist/lint/component-checks.d.ts +15 -1
- package/dist/lint/component-checks.d.ts.map +1 -1
- package/dist/lint/component-checks.js +1 -0
- package/dist/lint/presets/relaxed.json +2 -1
- package/dist/lint/presets/strict.json +2 -1
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
- package/dist/lint/rules/comp/index.d.ts +2 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/comp/index.js +4 -1
- package/dist/observation.d.ts +19 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +18 -1
- package/dist/op/activities/activity-contracts.d.ts +1 -1
- package/dist/op/activities/activity-contracts.js +1 -1
- package/dist/op/activities/change-set.d.ts +6 -4
- package/dist/op/activities/change-set.d.ts.map +1 -1
- package/dist/op/activities/change-set.js +4 -2
- package/dist/op/activity-failure.d.ts +27 -0
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-failure.js +31 -0
- package/dist/op/index.d.ts +2 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +5 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -8
- package/dist/pr-loop.d.ts +92 -3
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +62 -3
- package/dist/pulumi-urn.d.ts +101 -0
- package/dist/pulumi-urn.d.ts.map +1 -0
- package/dist/pulumi-urn.js +131 -0
- package/dist/release-floors.d.ts +23 -0
- package/dist/release-floors.d.ts.map +1 -0
- package/dist/release-floors.js +22 -0
- package/dist/sensitive-key.d.ts +23 -0
- package/dist/sensitive-key.d.ts.map +1 -0
- package/dist/sensitive-key.js +31 -0
- package/dist/wave-runtime.mjs +234 -234
- package/dist/workspace/adoption-report.d.ts +53 -0
- package/dist/workspace/adoption-report.d.ts.map +1 -0
- package/dist/workspace/adoption-report.js +76 -0
- package/dist/workspace/capabilities-contract.d.ts +7 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -1
- package/dist/workspace/capabilities-contract.js +2 -1
- package/dist/workspace/capabilities.schema.json +98 -0
- package/dist/workspace/change-set.schema.json +42 -3
- package/dist/workspace/ci-cloud-identity.d.ts +13 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +30 -1
- package/dist/workspace/compose-graph.d.ts +10 -1
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +6 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +6 -1
- package/dist/workspace/drift-cli.d.ts +81 -0
- package/dist/workspace/drift-cli.d.ts.map +1 -0
- package/dist/workspace/drift-cli.js +317 -0
- package/dist/workspace/drift.schema.json +672 -0
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +3 -0
- package/dist/workspace/graph.schema.json +72 -0
- package/dist/workspace/init.d.ts +5 -1
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/init.js +55 -9
- package/dist/workspace/ledger.schema.json +1 -1
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +2 -0
- package/dist/workspace/pr-report.schema.json +76 -3
- package/dist/workspace/promote-waves.d.ts +112 -0
- package/dist/workspace/promote-waves.d.ts.map +1 -0
- package/dist/workspace/promote-waves.js +290 -0
- package/dist/workspace/promote.d.ts +84 -2
- package/dist/workspace/promote.d.ts.map +1 -1
- package/dist/workspace/promote.js +347 -61
- package/dist/workspace/reason-codes.d.ts +12 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +14 -0
- package/dist/workspace/search-contract.d.ts +3 -3
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/search-contract.js +2 -0
- package/dist/workspace/search.schema.json +103 -5
- package/package.json +14 -1
- package/src/__fixtures__/drift-record/README.md +58 -0
- package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
- package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
- package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
- package/src/__fixtures__/drift-record/component-status.json +91 -0
- package/src/__fixtures__/drift-record/deep-diff.json +259 -0
- package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
- package/src/__fixtures__/drift-record/live-diff.json +125 -0
- package/src/audit/discover.e2e.test.ts +14 -0
- package/src/audit/discover.ts +3 -1
- package/src/behaviour.ts +1 -1
- package/src/capabilities/templates.json +145 -0
- package/src/capabilities/templates.test.ts +212 -0
- package/src/capabilities/templates.ts +204 -0
- package/src/change-set-bundle.test.ts +83 -0
- package/src/change-set-graph.ts +80 -9
- package/src/change-set.test.ts +24 -0
- package/src/change-set.ts +77 -14
- package/src/ci-identity-targets.test.ts +23 -0
- package/src/ci-identity.ts +10 -4
- package/src/cli/commands/import-index.test.ts +117 -0
- package/src/cli/commands/import-index.ts +265 -0
- package/src/cli/commands/lint.ts +4 -2
- package/src/cli/handlers/components.test.ts +9 -0
- package/src/cli/handlers/components.ts +12 -2
- package/src/cli/handlers/fan-out.ts +17 -3
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +26 -4
- package/src/cli/handlers/index-cloud.ts +32 -0
- package/src/cli/handlers/index-diff.test.ts +68 -0
- package/src/cli/handlers/index-diff.ts +146 -0
- package/src/cli/handlers/ledger-root-member.test.ts +161 -0
- package/src/cli/handlers/lifecycle-drift.ts +68 -0
- package/src/cli/handlers/lifecycle.test.ts +33 -11
- package/src/cli/handlers/lifecycle.ts +205 -30
- package/src/cli/handlers/misc.ts +73 -0
- package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
- package/src/cli/handlers/pr.ts +16 -3
- package/src/cli/handlers/promote.test.ts +64 -0
- package/src/cli/handlers/promote.ts +81 -6
- package/src/cli/handlers/query.ts +27 -4
- package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
- package/src/cli/handlers/run-groups-cli.test.ts +229 -0
- package/src/cli/handlers/scenario.test.ts +13 -0
- package/src/cli/handlers/scenario.ts +8 -7
- package/src/cli/handlers/search-index.test.ts +255 -0
- package/src/cli/handlers/search-json.test.ts +1 -1
- package/src/cli/handlers/search-ownership.test.ts +194 -0
- package/src/cli/handlers/search.ts +271 -38
- package/src/cli/main.test.ts +7 -0
- package/src/cli/main.ts +56 -1
- package/src/cli/mcp/lifecycle-tools.ts +3 -2
- package/src/cli/mcp/resource-handlers.ts +4 -3
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/cli/parse-args.ts +40 -2
- package/src/cli/registry.ts +19 -1
- package/src/cloud-lister/aws.test.ts +202 -0
- package/src/cloud-lister/aws.ts +226 -0
- package/src/cloud-lister/azure.test.ts +132 -0
- package/src/cloud-lister/azure.ts +142 -0
- package/src/cloud-lister/common.ts +115 -0
- package/src/cloud-lister/credentials.ts +110 -0
- package/src/cloud-lister/fakes.ts +66 -0
- package/src/cloud-lister/gcp.test.ts +121 -0
- package/src/cloud-lister/gcp.ts +159 -0
- package/src/cloud-lister/guard.test.ts +198 -0
- package/src/cloud-lister/guard.ts +111 -0
- package/src/cloud-lister/ids.ts +54 -0
- package/src/cloud-lister/index.ts +134 -0
- package/src/cloud-lister/kubernetes.test.ts +160 -0
- package/src/cloud-lister/kubernetes.ts +284 -0
- package/src/cloud-lister/markers.ts +74 -0
- package/src/cloud-lister/mask.ts +77 -0
- package/src/cloud-lister/shape.test.ts +81 -0
- package/src/cloud-lister/sigv4.ts +70 -0
- package/src/cloud-lister/transport.ts +118 -0
- package/src/cloud-lister/types.ts +135 -0
- package/src/components/capability-plugin-loader.ts +4 -0
- package/src/components/capability-plugin.ts +10 -0
- package/src/components/capability.ts +238 -0
- package/src/components/cli-support.ts +70 -14
- package/src/components/covered-round.test.ts +166 -0
- package/src/components/covered-round.ts +293 -0
- package/src/components/declared-reads.test.ts +103 -0
- package/src/components/declared-reads.ts +134 -0
- package/src/components/deploy-units.ts +4 -0
- package/src/components/driver.ts +101 -4
- package/src/components/fan-out-run.ts +95 -15
- package/src/components/fan-out-support.ts +10 -1
- package/src/components/fan-out.ts +30 -0
- package/src/components/groups.test.ts +320 -0
- package/src/components/groups.ts +880 -0
- package/src/components/index.ts +59 -0
- package/src/components/plan-shape-conformance.ts +130 -0
- package/src/components/pr-env-run-bundle.test.ts +2 -0
- package/src/components/pr-run.test.ts +728 -4
- package/src/components/pr-run.ts +395 -55
- package/src/components/promote-rounds.ts +25 -2
- package/src/components/promote.ts +55 -5
- package/src/components/registry.test.ts +1 -1
- package/src/components/release-gate.ts +115 -30
- package/src/components/release-groups.test.ts +362 -0
- package/src/components/run-groups.ts +113 -0
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +1 -0
- package/src/components/verbs/member-outputs.test.ts +205 -0
- package/src/components/verbs/member-outputs.ts +308 -0
- package/src/components/wave-plan.ts +5 -2
- package/src/config.ts +44 -0
- package/src/deep-observation.ts +5 -23
- package/src/drift-record.test.ts +596 -0
- package/src/drift-record.ts +1206 -0
- package/src/engine-facts.guard.test.ts +111 -0
- package/src/engine-facts.test.ts +178 -0
- package/src/engine-facts.ts +154 -0
- package/src/gated-waves-bundle.test.ts +2 -0
- package/src/graph-ir-live.test.ts +92 -0
- package/src/graph-ir.ts +317 -7
- package/src/graph-refs.ts +416 -0
- package/src/graph-schema/fixtures/adopt.json +15 -0
- package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/src/graph-schema/fixtures/index.json +68 -0
- package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/src/graph-schema/fixtures.ts +267 -0
- package/src/graph-schema/generate.ts +263 -0
- package/src/graph-schema/graph-schema.test.ts +153 -0
- package/src/graph-schema/graph.schema.json +1387 -0
- package/src/health.schema.json +2 -1
- package/src/identity.ts +2 -0
- package/src/index.ts +2 -0
- package/src/instance-nodes.ts +288 -0
- package/src/lexicon.ts +76 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
- package/src/lifecycle/adoption-file.test.ts +100 -0
- package/src/lifecycle/adoption-file.ts +154 -0
- package/src/lifecycle/cloud-index.test.ts +517 -0
- package/src/lifecycle/cloud-index.ts +736 -0
- package/src/lifecycle/derive-edges.test.ts +193 -0
- package/src/lifecycle/derive-edges.ts +182 -0
- package/src/lifecycle/git.ts +10 -4
- package/src/lifecycle/identity-match.test.ts +308 -0
- package/src/lifecycle/identity-match.ts +257 -0
- package/src/lifecycle/index-diff.test.ts +198 -0
- package/src/lifecycle/index-diff.ts +476 -0
- package/src/lifecycle/index-document.test.ts +64 -0
- package/src/lifecycle/index-document.ts +299 -0
- package/src/lifecycle/index-drift.test.ts +191 -0
- package/src/lifecycle/index-drift.ts +304 -0
- package/src/lifecycle/index-import.test.ts +255 -0
- package/src/lifecycle/index-import.ts +634 -0
- package/src/lifecycle/index-join.test.ts +250 -0
- package/src/lifecycle/index-join.ts +304 -0
- package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/member-ledger.test.ts +25 -7
- package/src/lifecycle/observation-baseline.test.ts +72 -1
- package/src/lifecycle/observation-baseline.ts +190 -27
- package/src/lifecycle/observe.ts +6 -1
- package/src/lifecycle/query.test.ts +69 -1
- package/src/lifecycle/query.ts +103 -10
- package/src/lifecycle/replay.test.ts +9 -5
- package/src/lifecycle/replay.ts +9 -5
- package/src/lifecycle/scenario.ts +4 -3
- package/src/lifecycle/snapshot-store-fake.ts +29 -0
- package/src/lifecycle/snapshot-store-s3.ts +316 -0
- package/src/lifecycle/snapshot-store.test.ts +385 -0
- package/src/lifecycle/snapshot-store.ts +981 -0
- package/src/lifecycle/snapshot.test.ts +13 -0
- package/src/lifecycle/snapshot.ts +23 -6
- package/src/lifecycle/type-listing.test.ts +166 -0
- package/src/lifecycle/type-listing.ts +309 -0
- package/src/lint/component-checks.ts +17 -1
- package/src/lint/config.test.ts +3 -0
- package/src/lint/presets/relaxed.json +2 -1
- package/src/lint/presets/strict.json +2 -1
- package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
- package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
- package/src/lint/rules/comp/comp.test.ts +2 -1
- package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
- package/src/lint/rules/comp/comp008.test.ts +77 -0
- package/src/lint/rules/comp/index.ts +4 -1
- package/src/observation-edges.test.ts +33 -0
- package/src/observation.ts +36 -1
- package/src/op/activities/activity-contracts.ts +1 -1
- package/src/op/activities/change-set.ts +8 -5
- package/src/op/activity-failure.ts +36 -0
- package/src/op/index.ts +2 -1
- package/src/op/local-executor.test.ts +80 -0
- package/src/op/local-executor.ts +27 -9
- package/src/pr-loop-bundle.test.ts +5 -2
- package/src/pr-loop.test.ts +57 -1
- package/src/pr-loop.ts +154 -6
- package/src/pulumi-urn.ts +151 -0
- package/src/release-floors.ts +22 -0
- package/src/sensitive-key.ts +34 -0
- package/src/workspace/adoption-report.test.ts +61 -0
- package/src/workspace/adoption-report.ts +103 -0
- package/src/workspace/capabilities-contract.test.ts +4 -0
- package/src/workspace/capabilities-contract.ts +9 -1
- package/src/workspace/capabilities.schema.json +98 -0
- package/src/workspace/change-set.schema.json +42 -3
- package/src/workspace/ci-cloud-identity.ts +31 -1
- package/src/workspace/compose-graph.test.ts +12 -0
- package/src/workspace/compose-graph.ts +12 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.ts +6 -1
- package/src/workspace/drift-cli.test.ts +65 -0
- package/src/workspace/drift-cli.ts +365 -0
- package/src/workspace/drift.schema.json +672 -0
- package/src/workspace/graph-cli.ts +5 -0
- package/src/workspace/graph.schema.json +72 -0
- package/src/workspace/init.test.ts +38 -1
- package/src/workspace/init.ts +50 -8
- package/src/workspace/intent-gaps.test.ts +18 -2
- package/src/workspace/ledger.schema.json +1 -1
- package/src/workspace/nested-graph.ts +1 -0
- package/src/workspace/pr-report.schema.json +76 -3
- package/src/workspace/promote-waves.test.ts +296 -0
- package/src/workspace/promote-waves.ts +340 -0
- package/src/workspace/promote.test.ts +158 -4
- package/src/workspace/promote.ts +398 -57
- package/src/workspace/reason-codes.test.ts +4 -1
- package/src/workspace/reason-codes.ts +14 -0
- package/src/workspace/search-contract.ts +2 -0
- package/src/workspace/search.schema.json +103 -5
package/src/graph-refs.ts
CHANGED
|
@@ -36,8 +36,26 @@ export interface RefRule {
|
|
|
36
36
|
match?: "id" | "arn" | "name" | "any";
|
|
37
37
|
/** Constrain the target kind (disambiguates identifier collisions). */
|
|
38
38
|
targetKind?: string;
|
|
39
|
+
/**
|
|
40
|
+
* The target kind when the rule runs over indexed nodes (#4522, ruled on
|
|
41
|
+
* INTENTIUS/behold#1023 Q4), for a rule that has no {@link targetKind}
|
|
42
|
+
* because its values are logical ids (`enrichLiveAttrs`), which are unique.
|
|
43
|
+
* Over an index the same path holds a physical value, often a name, so the
|
|
44
|
+
* target kind is what keeps it from joining whatever else has that name.
|
|
45
|
+
*/
|
|
46
|
+
indexedTargetKind?: string;
|
|
39
47
|
/** `reference` → an edge; `containment` → a boundary hint (#779), not an edge. */
|
|
40
48
|
relation: "reference" | "containment";
|
|
49
|
+
/**
|
|
50
|
+
* The path holds a reference only sometimes (#4535): an env var's value, a
|
|
51
|
+
* selector's label, a binding subject that may be a user. A value that
|
|
52
|
+
* names no node of the rule's kind is then not reported as dangling, since
|
|
53
|
+
* most such values are not references at all, and a path read by several
|
|
54
|
+
* rules (one per target kind) would otherwise report every value matched by
|
|
55
|
+
* one rule as dangling under the others. A match is still a typed, scoped
|
|
56
|
+
* match, and two candidates still draw no edge.
|
|
57
|
+
*/
|
|
58
|
+
optional?: boolean;
|
|
41
59
|
/** Edge / containment label (e.g. "in VPC", "sg", "targets"). */
|
|
42
60
|
label?: string;
|
|
43
61
|
/**
|
|
@@ -61,10 +79,29 @@ export interface RefRule {
|
|
|
61
79
|
viaAttr?: string;
|
|
62
80
|
}
|
|
63
81
|
|
|
82
|
+
/**
|
|
83
|
+
* A typed tag rule (#4522, ruled on INTENTIUS/behold#1023 Q5): a tag whose
|
|
84
|
+
* key names a cloud id, read off an indexed node's `tags`. Tags make edges
|
|
85
|
+
* only through these, never by matching equal tag values, because two
|
|
86
|
+
* objects can carry the same value for any reason.
|
|
87
|
+
*/
|
|
88
|
+
export interface TagRefRule {
|
|
89
|
+
/** Holder kind; absent for every kind of this lexicon. */
|
|
90
|
+
from?: string;
|
|
91
|
+
/** The exact tag key, or a key prefix ending in `/` whose remainder is the value. */
|
|
92
|
+
key: string;
|
|
93
|
+
/** `value`: the tag's value is the id. `key-suffix`: the key after {@link key} is (`kubernetes.io/cluster/<name>`). */
|
|
94
|
+
read: "value" | "key-suffix";
|
|
95
|
+
targetKind: string;
|
|
96
|
+
label?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
64
99
|
/** A lexicon's reference knowledge — its identity map and reference rules. */
|
|
65
100
|
export interface ReferenceCatalog {
|
|
66
101
|
identities: IdentityRule[];
|
|
67
102
|
refs: RefRule[];
|
|
103
|
+
/** Typed tag rules, used over indexed nodes only (#4522). */
|
|
104
|
+
tagRefs?: TagRefRule[];
|
|
68
105
|
}
|
|
69
106
|
|
|
70
107
|
/** `child` is contained by `parent` (subnet ∈ VPC). For #779's boundary boxes. */
|
|
@@ -193,8 +230,12 @@ export function reconstructEdges(nodes: IRNode[], catalog: ReferenceCatalog): Re
|
|
|
193
230
|
if (rule.from !== node.kind) continue;
|
|
194
231
|
for (const value of readPath(node.attrs, rule.path)) {
|
|
195
232
|
const candidates = index.get(value) ?? [];
|
|
233
|
+
// `chant graph --live` keeps its first-candidate pick: its values are
|
|
234
|
+
// logical ids and physical ids, which name one node. An index refuses
|
|
235
|
+
// the pick instead (reconstructIndexedEdges, #4522).
|
|
196
236
|
const match = rule.targetKind ? candidates.find((c) => c.kind === rule.targetKind) : candidates[0];
|
|
197
237
|
if (!match) {
|
|
238
|
+
if (rule.optional) continue;
|
|
198
239
|
dangling.push({ from: node.id, path: rule.path, value, ...(rule.targetKind ? { targetKind: rule.targetKind } : {}) });
|
|
199
240
|
continue;
|
|
200
241
|
}
|
|
@@ -261,3 +302,378 @@ export function containmentGroups(pairs: ContainmentPair[]): Record<string, stri
|
|
|
261
302
|
for (const parent of [...byParent.keys()].sort()) out[parent] = [...byParent.get(parent)!].sort();
|
|
262
303
|
return out;
|
|
263
304
|
}
|
|
305
|
+
|
|
306
|
+
// ── Indexed nodes (#4522) ────────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
/** One lexicon's catalog, named, so a derived edge can say which rule made it. */
|
|
309
|
+
export interface NamedCatalog {
|
|
310
|
+
lexicon: string;
|
|
311
|
+
catalog: ReferenceCatalog;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Where an indexed node's identifiers are unique (INTENTIUS/behold#1023 Q2):
|
|
316
|
+
* its substrate and account, subscription, project or cluster, plus its
|
|
317
|
+
* region for a regional type. A global type (IAM, Route 53, CloudFront) has
|
|
318
|
+
* no region, and is matched by account only.
|
|
319
|
+
*/
|
|
320
|
+
export interface IndexedNodeScope {
|
|
321
|
+
/** `<substrate>/<scopeId>`. */
|
|
322
|
+
account: string;
|
|
323
|
+
/** The region or location; undefined for a global type. */
|
|
324
|
+
region?: string;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** A reference whose value matched several nodes: no edge is drawn (behold#1023 Q3). */
|
|
328
|
+
export interface AmbiguousRef {
|
|
329
|
+
from: string;
|
|
330
|
+
rule: string;
|
|
331
|
+
path: string;
|
|
332
|
+
value: string;
|
|
333
|
+
/** Every node the value matched, sorted. */
|
|
334
|
+
candidates: string[];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** A reference whose target is not in the index, with the rule that read it. */
|
|
338
|
+
export interface IndexedDanglingRef extends DanglingRef {
|
|
339
|
+
rule: string;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export interface IndexedEdges {
|
|
343
|
+
/** Reference and containment edges, each `origin: "derived"` with its `rule`. */
|
|
344
|
+
edges: IREdge[];
|
|
345
|
+
containment: ContainmentPair[];
|
|
346
|
+
dangling: IndexedDanglingRef[];
|
|
347
|
+
ambiguous: AmbiguousRef[];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Values a masking or normalization pass wrote: never an identifier. */
|
|
351
|
+
const PLACEHOLDER_VALUES: ReadonlySet<string> = new Set(["[REDACTED]", "<chant:unresolved>", "<masked>"]);
|
|
352
|
+
|
|
353
|
+
/** The attribute a rule reads through, as a declared graph's `viaAttr` names it: `SecurityGroupIds[]` is `SecurityGroupIds`. */
|
|
354
|
+
function topAttr(path: string): string {
|
|
355
|
+
const first = path.split(".")[0];
|
|
356
|
+
return first.endsWith("[]") ? first.slice(0, -2) : first;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** `<lexicon>:<from kind>:<path>`, the id a derived edge carries. */
|
|
360
|
+
export function refRuleId(lexicon: string, from: string, path: string): string {
|
|
361
|
+
return `${lexicon}:${from}:${path}`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const byString = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0);
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Derive edges over indexed nodes (#4522) with each lexicon's catalog, under
|
|
368
|
+
* the rules ruled on INTENTIUS/behold#1023:
|
|
369
|
+
*
|
|
370
|
+
* - the join key is the scoped value: a reference on a node in region R
|
|
371
|
+
* matches nodes of the same account in R, and global nodes of the same
|
|
372
|
+
* account; a reference on a global node matches global nodes only;
|
|
373
|
+
* - a rule's target kind is `targetKind`, else `indexedTargetKind`;
|
|
374
|
+
* - a value that matches more than one node draws no edge and is returned in
|
|
375
|
+
* `ambiguous` with every candidate, in place of {@link reconstructEdges}'
|
|
376
|
+
* first-candidate pick, so two reads of one account in another node order
|
|
377
|
+
* give the same edges;
|
|
378
|
+
* - tags make edges only through a catalog's typed `tagRefs`.
|
|
379
|
+
*
|
|
380
|
+
* A node is matched by its id, physical id, native id, cloud ids, and the
|
|
381
|
+
* catalog's identity paths. Rules apply to nodes of their own lexicon. Pure.
|
|
382
|
+
*/
|
|
383
|
+
export function reconstructIndexedEdges(
|
|
384
|
+
nodes: readonly IRNode[],
|
|
385
|
+
catalogs: readonly NamedCatalog[],
|
|
386
|
+
scopeOf: (node: IRNode) => IndexedNodeScope,
|
|
387
|
+
): IndexedEdges {
|
|
388
|
+
const sorted = [...nodes].sort((a, b) => byString(a.id, b.id));
|
|
389
|
+
const keyOf = (scope: IndexedNodeScope, value: string) => `${scope.account}\u0000${scope.region ?? ""}\u0000${value}`;
|
|
390
|
+
const index = new Map<string, Map<string, string>>();
|
|
391
|
+
const add = (scope: IndexedNodeScope, value: string | undefined, node: IRNode): void => {
|
|
392
|
+
if (value === undefined || value === "" || PLACEHOLDER_VALUES.has(value)) return;
|
|
393
|
+
const k = keyOf(scope, value);
|
|
394
|
+
(index.get(k) ?? index.set(k, new Map()).get(k)!).set(node.id, node.kind);
|
|
395
|
+
};
|
|
396
|
+
const identities = catalogs.flatMap((c) => c.catalog.identities);
|
|
397
|
+
for (const node of sorted) {
|
|
398
|
+
const scope = scopeOf(node);
|
|
399
|
+
add(scope, node.id, node);
|
|
400
|
+
add(scope, node.physicalId, node);
|
|
401
|
+
add(scope, node.nativeId, node);
|
|
402
|
+
for (const c of node.facts?.cloudIds ?? []) add(scope, c.value, node);
|
|
403
|
+
for (const rule of identities) {
|
|
404
|
+
if (rule.kind !== node.kind) continue;
|
|
405
|
+
for (const p of rule.ids) for (const v of readPath(node.attrs, p)) add(scope, v, node);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const lookup = (holder: IRNode, value: string): Map<string, string> => {
|
|
410
|
+
const scope = scopeOf(holder);
|
|
411
|
+
const out = new Map<string, string>();
|
|
412
|
+
const keys = scope.region !== undefined ? [keyOf(scope, value), keyOf({ account: scope.account }, value)] : [keyOf(scope, value)];
|
|
413
|
+
for (const k of keys) for (const [id, kind] of index.get(k) ?? []) out.set(id, kind);
|
|
414
|
+
return out;
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const edges = new Map<string, IREdge>();
|
|
418
|
+
const containment = new Map<string, ContainmentPair>();
|
|
419
|
+
// Keyed by holder, rule and value: two rules with one id (the describe-shape
|
|
420
|
+
// and template-shape rules for the same path) report a reference once.
|
|
421
|
+
const dangling = new Map<string, IndexedDanglingRef>();
|
|
422
|
+
const ambiguous = new Map<string, AmbiguousRef>();
|
|
423
|
+
|
|
424
|
+
const resolve = (
|
|
425
|
+
holder: IRNode,
|
|
426
|
+
value: string,
|
|
427
|
+
rule: { id: string; path: string; targetKind?: string; relation: "reference" | "containment"; via: string; label?: string; optional?: boolean },
|
|
428
|
+
): void => {
|
|
429
|
+
if (PLACEHOLDER_VALUES.has(value)) return;
|
|
430
|
+
const found = lookup(holder, value);
|
|
431
|
+
let candidates = [...found.entries()].filter(([, kind]) => rule.targetKind === undefined || kind === rule.targetKind).map(([id]) => id);
|
|
432
|
+
if (candidates.includes(holder.id)) {
|
|
433
|
+
if (candidates.length === 1) return; // a self-reference (an SG rule to its own group)
|
|
434
|
+
candidates = candidates.filter((id) => id !== holder.id);
|
|
435
|
+
}
|
|
436
|
+
candidates.sort(byString);
|
|
437
|
+
if (candidates.length === 0) {
|
|
438
|
+
if (rule.optional) return;
|
|
439
|
+
dangling.set(`${holder.id}|${rule.id}|${value}`, { from: holder.id, path: rule.path, value, rule: rule.id, ...(rule.targetKind ? { targetKind: rule.targetKind } : {}) });
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (candidates.length > 1) {
|
|
443
|
+
ambiguous.set(`${holder.id}|${rule.id}|${value}`, { from: holder.id, rule: rule.id, path: rule.path, value, candidates });
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const to = candidates[0];
|
|
447
|
+
const key = `${holder.id}|${to}|${rule.via}|${rule.relation}`;
|
|
448
|
+
const prev = edges.get(key);
|
|
449
|
+
// Two rules can draw one edge (the describe-shape and template-shape rules
|
|
450
|
+
// read the same attribute); the smaller rule id is kept, so the result
|
|
451
|
+
// does not depend on catalog order.
|
|
452
|
+
if (!prev || byString(rule.id, prev.rule!) < 0) {
|
|
453
|
+
edges.set(key, {
|
|
454
|
+
from: holder.id,
|
|
455
|
+
to,
|
|
456
|
+
kind: "ref",
|
|
457
|
+
viaAttr: rule.via,
|
|
458
|
+
grain: "property",
|
|
459
|
+
origin: "derived",
|
|
460
|
+
rule: rule.id,
|
|
461
|
+
relation: rule.relation,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
if (rule.relation === "containment") {
|
|
465
|
+
const ck = `${holder.id}|${to}`;
|
|
466
|
+
const prevPair = containment.get(ck);
|
|
467
|
+
if (!prevPair || (rule.label !== undefined && (prevPair.label === undefined || byString(rule.label, prevPair.label) < 0))) {
|
|
468
|
+
containment.set(ck, { child: holder.id, parent: to, ...(rule.label ? { label: rule.label } : {}) });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
for (const node of sorted) {
|
|
474
|
+
for (const { lexicon, catalog } of catalogs) {
|
|
475
|
+
if (node.lexicon !== lexicon) continue;
|
|
476
|
+
for (const rule of catalog.refs) {
|
|
477
|
+
if (rule.from !== node.kind) continue;
|
|
478
|
+
const spec = {
|
|
479
|
+
id: refRuleId(lexicon, rule.from, rule.path),
|
|
480
|
+
path: rule.path,
|
|
481
|
+
targetKind: rule.targetKind ?? rule.indexedTargetKind,
|
|
482
|
+
relation: rule.relation,
|
|
483
|
+
via: rule.viaAttr ?? topAttr(rule.path),
|
|
484
|
+
...(rule.label ? { label: rule.label } : {}),
|
|
485
|
+
...(rule.optional ? { optional: true } : {}),
|
|
486
|
+
};
|
|
487
|
+
for (const value of new Set(readPath(node.attrs, rule.path))) resolve(node, value, spec);
|
|
488
|
+
}
|
|
489
|
+
for (const rule of catalog.tagRefs ?? []) {
|
|
490
|
+
if (rule.from !== undefined && rule.from !== node.kind) continue;
|
|
491
|
+
const tags = node.tags ?? {};
|
|
492
|
+
const spec = {
|
|
493
|
+
id: refRuleId(lexicon, rule.from ?? node.kind, `tag:${rule.key}`),
|
|
494
|
+
path: `tag:${rule.key}`,
|
|
495
|
+
targetKind: rule.targetKind,
|
|
496
|
+
relation: "reference" as const,
|
|
497
|
+
via: "Tags",
|
|
498
|
+
...(rule.label ? { label: rule.label } : {}),
|
|
499
|
+
};
|
|
500
|
+
if (rule.read === "value") {
|
|
501
|
+
const v = tags[rule.key];
|
|
502
|
+
if (typeof v === "string" && v !== "") resolve(node, v, spec);
|
|
503
|
+
} else {
|
|
504
|
+
for (const k of Object.keys(tags).sort()) {
|
|
505
|
+
if (k.startsWith(rule.key) && k.length > rule.key.length) resolve(node, k.slice(rule.key.length), spec);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const edgeKey = (e: IREdge) => `${e.from}|${e.to}|${e.viaAttr ?? ""}|${e.relation ?? ""}`;
|
|
513
|
+
return {
|
|
514
|
+
edges: [...edges.values()].sort((a, b) => byString(edgeKey(a), edgeKey(b))),
|
|
515
|
+
containment: [...containment.values()].sort((a, b) => byString(`${a.child}|${a.parent}`, `${b.child}|${b.parent}`)),
|
|
516
|
+
dangling: [...dangling.values()].sort((a, b) => byString(`${a.from}|${a.rule}|${a.value}`, `${b.from}|${b.rule}|${b.value}`)),
|
|
517
|
+
ambiguous: [...ambiguous.values()].sort((a, b) => byString(`${a.from}|${a.rule}|${a.value}`, `${b.from}|${b.rule}|${b.value}`)),
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ── Identity matches over indexed nodes (#4523) ──────────────────────────────
|
|
522
|
+
|
|
523
|
+
/** One value that names an indexed node: its native id, or one of its cloud ids. */
|
|
524
|
+
export interface NodeIdentityValue {
|
|
525
|
+
/** `native-id`, or the cloud id's kind (`arn`, `provider-id`, ...). */
|
|
526
|
+
kind: string;
|
|
527
|
+
value: string;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** The lexicon whose nodes can be identity-matched, and the values that name each one. */
|
|
531
|
+
export interface IdentityOwners {
|
|
532
|
+
lexicon: string;
|
|
533
|
+
/** The values that name a node of this lexicon. Absent: its native id and its ARN cloud ids. */
|
|
534
|
+
identities?(node: IRNode): NodeIdentityValue[];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** A value that named several nodes: no identity-match edge is drawn (behold#1023 Q3). */
|
|
538
|
+
export interface AmbiguousIdentityRef {
|
|
539
|
+
from: string;
|
|
540
|
+
path: string;
|
|
541
|
+
value: string;
|
|
542
|
+
/** Every node the value named, sorted. */
|
|
543
|
+
candidates: string[];
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export interface IdentityEdges {
|
|
547
|
+
/** Each `origin: "derived"`, `rule: "identity-match:..."`. */
|
|
548
|
+
edges: IREdge[];
|
|
549
|
+
ambiguous: AmbiguousIdentityRef[];
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** The label every edge of the identity pass carries, as the prefix of its `rule`. */
|
|
553
|
+
export const IDENTITY_MATCH_RULE = "identity-match";
|
|
554
|
+
|
|
555
|
+
/** `identity-match:<owner lexicon>:<owner kind>:<identity kind>`. */
|
|
556
|
+
export function identityMatchRuleId(lexicon: string, kind: string, identityKind: string): string {
|
|
557
|
+
return `${IDENTITY_MATCH_RULE}:${lexicon}:${kind}:${identityKind}`;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** The default identities of an indexed node: the native id, and any ARN among its cloud ids. */
|
|
561
|
+
export function defaultNodeIdentities(node: IRNode): NodeIdentityValue[] {
|
|
562
|
+
const out: NodeIdentityValue[] = [];
|
|
563
|
+
if (node.nativeId) out.push({ kind: "native-id", value: node.nativeId });
|
|
564
|
+
for (const c of node.facts?.cloudIds ?? []) if (c.kind === "arn") out.push({ kind: "arn", value: c.value });
|
|
565
|
+
return out;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/** Top-level keys whose values the identity pass never reads: tags make edges only through typed rules (behold#1023 Q5). */
|
|
569
|
+
const IDENTITY_SKIPPED_KEYS: ReadonlySet<string> = new Set(["Tags", "tags", "TagSet", "labels", "Labels"]);
|
|
570
|
+
|
|
571
|
+
/** Short values are names anything can carry (`default`, `app`); whitespace means prose, not an identifier. */
|
|
572
|
+
const MIN_IDENTITY_VALUE = 6;
|
|
573
|
+
const MAX_IDENTITY_VALUE = 2048;
|
|
574
|
+
|
|
575
|
+
function identityCandidateValue(v: string): boolean {
|
|
576
|
+
return v.length >= MIN_IDENTITY_VALUE && v.length <= MAX_IDENTITY_VALUE && !/\s/.test(v) && !PLACEHOLDER_VALUES.has(v);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** Every string leaf under `attrs` with its path (`a.b`, `arr[].c`), skipping tag subtrees. */
|
|
580
|
+
function stringLeaves(attrs: Record<string, unknown>): Array<{ path: string; value: string }> {
|
|
581
|
+
const out: Array<{ path: string; value: string }> = [];
|
|
582
|
+
const walk = (v: unknown, path: string): void => {
|
|
583
|
+
if (typeof v === "string") {
|
|
584
|
+
out.push({ path, value: v });
|
|
585
|
+
} else if (Array.isArray(v)) {
|
|
586
|
+
for (const x of v) walk(x, `${path}[]`);
|
|
587
|
+
} else if (v && typeof v === "object") {
|
|
588
|
+
for (const k of Object.keys(v as Record<string, unknown>).sort()) walk((v as Record<string, unknown>)[k], path ? `${path}.${k}` : k);
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
for (const k of Object.keys(attrs).sort()) {
|
|
592
|
+
if (IDENTITY_SKIPPED_KEYS.has(k)) continue;
|
|
593
|
+
walk(attrs[k], k);
|
|
594
|
+
}
|
|
595
|
+
return out;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* The identity-match pass (#4523, ruled on INTENTIUS/behold#1023 Q1-Q3): the
|
|
600
|
+
* second pass over indexed nodes, after {@link reconstructIndexedEdges}. A
|
|
601
|
+
* string attribute on one node equal to a value that names another node (its
|
|
602
|
+
* native id, which for aws is the CloudFormation `primaryIdentifier`, or its
|
|
603
|
+
* ARN) draws an edge labelled `identity-match`:
|
|
604
|
+
*
|
|
605
|
+
* - only nodes of a lexicon in `owners` are named; holders are any node;
|
|
606
|
+
* - the join key is the scoped value, as in the catalog pass: a holder in
|
|
607
|
+
* region R reaches nodes of its account in R and global nodes of its
|
|
608
|
+
* account; a global holder reaches global nodes only;
|
|
609
|
+
* - a value naming more than one node draws no edge and is returned in
|
|
610
|
+
* `ambiguous` with every candidate;
|
|
611
|
+
* - tag subtrees are never read, values under {@link MIN_IDENTITY_VALUE}
|
|
612
|
+
* characters or with whitespace are skipped, and a value naming the holder
|
|
613
|
+
* itself is not a reference;
|
|
614
|
+
* - a pair the catalog pass already joined (`existing`) gets no second edge.
|
|
615
|
+
*
|
|
616
|
+
* Pure, and independent of node order.
|
|
617
|
+
*/
|
|
618
|
+
export function reconstructIdentityEdges(
|
|
619
|
+
nodes: readonly IRNode[],
|
|
620
|
+
owners: readonly IdentityOwners[],
|
|
621
|
+
scopeOf: (node: IRNode) => IndexedNodeScope,
|
|
622
|
+
existing: readonly IREdge[] = [],
|
|
623
|
+
): IdentityEdges {
|
|
624
|
+
const sorted = [...nodes].sort((a, b) => byString(a.id, b.id));
|
|
625
|
+
const ownerBy = new Map(owners.map((o) => [o.lexicon, o]));
|
|
626
|
+
const keyOf = (scope: IndexedNodeScope, value: string) => `${scope.account}\u0000${scope.region ?? ""}\u0000${value}`;
|
|
627
|
+
// key -> node id -> the rule that names it
|
|
628
|
+
const index = new Map<string, Map<string, string>>();
|
|
629
|
+
const own = new Map<string, Set<string>>();
|
|
630
|
+
for (const node of sorted) {
|
|
631
|
+
const owner = ownerBy.get(node.lexicon);
|
|
632
|
+
if (!owner || node.unmappedKind) continue;
|
|
633
|
+
const scope = scopeOf(node);
|
|
634
|
+
const values = (owner.identities ?? defaultNodeIdentities)(node);
|
|
635
|
+
own.set(node.id, new Set(values.map((v) => v.value)));
|
|
636
|
+
for (const { kind, value } of values) {
|
|
637
|
+
if (!identityCandidateValue(value)) continue;
|
|
638
|
+
const k = keyOf(scope, value);
|
|
639
|
+
const m = index.get(k) ?? index.set(k, new Map()).get(k)!;
|
|
640
|
+
const rule = identityMatchRuleId(owner.lexicon, node.kind, kind);
|
|
641
|
+
const prev = m.get(node.id);
|
|
642
|
+
if (prev === undefined || byString(rule, prev) < 0) m.set(node.id, rule);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const joined = new Set(existing.map((e) => `${e.from}|${e.to}`));
|
|
647
|
+
const edges = new Map<string, IREdge>();
|
|
648
|
+
const ambiguous = new Map<string, AmbiguousIdentityRef>();
|
|
649
|
+
|
|
650
|
+
for (const holder of sorted) {
|
|
651
|
+
const scope = scopeOf(holder);
|
|
652
|
+
const keys = scope.region !== undefined ? (v: string) => [keyOf(scope, v), keyOf({ account: scope.account }, v)] : (v: string) => [keyOf(scope, v)];
|
|
653
|
+
const mine = own.get(holder.id) ?? new Set<string>([holder.nativeId ?? "", holder.physicalId ?? ""]);
|
|
654
|
+
for (const { path, value } of stringLeaves(holder.attrs ?? {})) {
|
|
655
|
+
if (!identityCandidateValue(value) || mine.has(value)) continue;
|
|
656
|
+
const found = new Map<string, string>();
|
|
657
|
+
for (const k of keys(value)) for (const [id, rule] of index.get(k) ?? []) if (id !== holder.id) found.set(id, rule);
|
|
658
|
+
if (found.size === 0) continue;
|
|
659
|
+
if (found.size > 1) {
|
|
660
|
+
ambiguous.set(`${holder.id}|${path}|${value}`, { from: holder.id, path, value, candidates: [...found.keys()].sort(byString) });
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
const [[to, rule]] = [...found.entries()];
|
|
664
|
+
if (joined.has(`${holder.id}|${to}`)) continue;
|
|
665
|
+
const via = topAttr(path);
|
|
666
|
+
const key = `${holder.id}|${to}|${via}`;
|
|
667
|
+
const prev = edges.get(key);
|
|
668
|
+
if (!prev || byString(rule, prev.rule!) < 0) {
|
|
669
|
+
edges.set(key, { from: holder.id, to, kind: "ref", viaAttr: via, grain: "property", origin: "derived", rule, relation: "reference" });
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const edgeKey = (e: IREdge) => `${e.from}|${e.to}|${e.viaAttr ?? ""}`;
|
|
675
|
+
return {
|
|
676
|
+
edges: [...edges.values()].sort((a, b) => byString(edgeKey(a), edgeKey(b))),
|
|
677
|
+
ambiguous: [...ambiguous.values()].sort((a, b) => byString(`${a.from}|${a.path}|${a.value}`, `${b.from}|${b.path}|${b.value}`)),
|
|
678
|
+
};
|
|
679
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adopt": "v1",
|
|
3
|
+
"index": {
|
|
4
|
+
"scopeKey": "cloud/aws/000000000000/9bd41033bb42",
|
|
5
|
+
"version": "20261026T120000Z-e2f92b265a4d.json",
|
|
6
|
+
"digest": "e2f92b265a4dd604e013b3f55eafe5cf317ea1f5b7c54b4c5187120d0dcfe10d"
|
|
7
|
+
},
|
|
8
|
+
"select": [
|
|
9
|
+
{
|
|
10
|
+
"node": "aws/000000000000/us-east-1/AWS::S3::Bucket/web-logs",
|
|
11
|
+
"as": "aws_s3_bucket.web_logs",
|
|
12
|
+
"type": "aws_s3_bucket"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"meta": {
|
|
4
|
+
"engineGraph": {
|
|
5
|
+
"format": 1,
|
|
6
|
+
"engine": {
|
|
7
|
+
"name": "choudoufu",
|
|
8
|
+
"version": "0.33.0"
|
|
9
|
+
},
|
|
10
|
+
"scope": {
|
|
11
|
+
"kind": "estate",
|
|
12
|
+
"estate": "web"
|
|
13
|
+
},
|
|
14
|
+
"observedAt": "2026-10-26T12:00:00Z",
|
|
15
|
+
"markerScheme": {
|
|
16
|
+
"ownerKey": "tofu-estate",
|
|
17
|
+
"addressKey": "tofu-address",
|
|
18
|
+
"continuationKeys": [
|
|
19
|
+
"tofu-slot"
|
|
20
|
+
],
|
|
21
|
+
"encoding": {
|
|
22
|
+
"tags": "escaped",
|
|
23
|
+
"labels": "escaped"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"gaps": [
|
|
27
|
+
{
|
|
28
|
+
"address": "aws_iam_role.legacy",
|
|
29
|
+
"reason": "unsupported-kind",
|
|
30
|
+
"detail": "no lister for this type on this provider"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"nodes": [
|
|
36
|
+
{
|
|
37
|
+
"id": "aws_s3_bucket.logs",
|
|
38
|
+
"kind": "aws_s3_bucket",
|
|
39
|
+
"lexicon": "terraform",
|
|
40
|
+
"attrs": {
|
|
41
|
+
"address": "aws_s3_bucket.logs"
|
|
42
|
+
},
|
|
43
|
+
"physicalId": "web-logs",
|
|
44
|
+
"facts": {
|
|
45
|
+
"ownership": "bound",
|
|
46
|
+
"ownedBy": "record",
|
|
47
|
+
"cloudIds": [
|
|
48
|
+
{
|
|
49
|
+
"kind": "import-id",
|
|
50
|
+
"value": "web-logs"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"kind": "arn",
|
|
54
|
+
"value": "arn:aws:s3:::web-logs"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"recordVersions": {
|
|
58
|
+
"kept": true,
|
|
59
|
+
"versions": [
|
|
60
|
+
{
|
|
61
|
+
"id": "3sL4kqtJlcpXroDTDmJ.rmSpXd3dIbrHY",
|
|
62
|
+
"writtenAt": "2026-10-26T11:58:02Z",
|
|
63
|
+
"current": true,
|
|
64
|
+
"deleted": false
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "2bE8x0l1BqzDmoPTmD7M7bGqFrsTFTQS",
|
|
68
|
+
"writtenAt": "2026-10-20T09:14:40Z",
|
|
69
|
+
"current": false,
|
|
70
|
+
"deleted": false
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"observedAt": "2026-10-26T12:00:00Z"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "module.app.aws_eip.pool",
|
|
79
|
+
"kind": "aws_eip",
|
|
80
|
+
"lexicon": "terraform",
|
|
81
|
+
"attrs": {
|
|
82
|
+
"address": "module.app.aws_eip.pool"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "module.app.aws_instance.web",
|
|
87
|
+
"kind": "aws_instance",
|
|
88
|
+
"lexicon": "terraform",
|
|
89
|
+
"attrs": {
|
|
90
|
+
"address": "module.app.aws_instance.web"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "module.app[0].aws_eip.pool[1]",
|
|
95
|
+
"kind": "aws_eip",
|
|
96
|
+
"lexicon": "terraform",
|
|
97
|
+
"attrs": {
|
|
98
|
+
"address": "module.app[0].aws_eip.pool[1]"
|
|
99
|
+
},
|
|
100
|
+
"physicalId": "eipalloc-0abc",
|
|
101
|
+
"instanceOf": "module.app.aws_eip.pool",
|
|
102
|
+
"instanceKey": "[1]",
|
|
103
|
+
"instanceScope": "module.app[0]",
|
|
104
|
+
"facts": {
|
|
105
|
+
"ownership": "bound",
|
|
106
|
+
"ownedBy": "marker",
|
|
107
|
+
"markers": {
|
|
108
|
+
"owner": "web",
|
|
109
|
+
"address": "module.app:0.aws_eip.pool:1",
|
|
110
|
+
"encoding": "escaped",
|
|
111
|
+
"source": "live"
|
|
112
|
+
},
|
|
113
|
+
"cloudIds": [
|
|
114
|
+
{
|
|
115
|
+
"kind": "import-id",
|
|
116
|
+
"value": "eipalloc-0abc"
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
"observedAt": "2026-10-26T12:00:00Z"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"id": "aws_sqs_queue.jobs",
|
|
124
|
+
"kind": "aws_sqs_queue",
|
|
125
|
+
"lexicon": "terraform",
|
|
126
|
+
"attrs": {
|
|
127
|
+
"address": "aws_sqs_queue.jobs"
|
|
128
|
+
},
|
|
129
|
+
"facts": {
|
|
130
|
+
"ownership": "foreign",
|
|
131
|
+
"markers": {
|
|
132
|
+
"owner": "billing",
|
|
133
|
+
"encoding": "escaped",
|
|
134
|
+
"source": "live"
|
|
135
|
+
},
|
|
136
|
+
"observedAt": "2026-10-26T12:00:00Z"
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"id": "aws_sns_topic.alerts",
|
|
141
|
+
"kind": "aws_sns_topic",
|
|
142
|
+
"lexicon": "terraform",
|
|
143
|
+
"attrs": {
|
|
144
|
+
"address": "aws_sns_topic.alerts"
|
|
145
|
+
},
|
|
146
|
+
"facts": {
|
|
147
|
+
"ownership": "pending",
|
|
148
|
+
"observedAt": "2026-10-26T12:00:00Z"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"edges": [
|
|
153
|
+
{
|
|
154
|
+
"from": "module.app[0].aws_eip.pool[1]",
|
|
155
|
+
"to": "module.app.aws_instance.web",
|
|
156
|
+
"kind": "ref",
|
|
157
|
+
"viaAttr": "dependencies",
|
|
158
|
+
"grain": "block"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"from": "module.app.aws_instance.web",
|
|
162
|
+
"to": "aws_s3_bucket.logs",
|
|
163
|
+
"kind": "ref",
|
|
164
|
+
"viaAttr": "dependencies",
|
|
165
|
+
"grain": "block"
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
"groups": {}
|
|
169
|
+
}
|