@intentius/chant 0.124.52 → 0.124.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/discover.js +3 -1
- package/dist/behaviour.d.ts +1 -1
- package/dist/behaviour.d.ts.map +1 -1
- package/dist/capabilities/templates.d.ts +65 -0
- package/dist/capabilities/templates.d.ts.map +1 -0
- package/dist/capabilities/templates.js +193 -0
- package/dist/capabilities/templates.json +145 -0
- package/dist/change-set-graph.d.ts +35 -3
- package/dist/change-set-graph.d.ts.map +1 -1
- package/dist/change-set-graph.js +60 -9
- package/dist/change-set.d.ts +65 -7
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +23 -12
- package/dist/ci-identity.d.ts +7 -2
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +4 -1
- package/dist/cli/commands/import-index.d.ts +52 -0
- package/dist/cli/commands/import-index.d.ts.map +1 -0
- package/dist/cli/commands/import-index.js +226 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +4 -2
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/components.js +11 -2
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +16 -3
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +22 -4
- package/dist/cli/handlers/index-cloud.d.ts +14 -0
- package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
- package/dist/cli/handlers/index-cloud.js +22 -0
- package/dist/cli/handlers/index-diff.d.ts +18 -0
- package/dist/cli/handlers/index-diff.d.ts.map +1 -0
- package/dist/cli/handlers/index-diff.js +142 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
- package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-drift.js +64 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.js +188 -28
- package/dist/cli/handlers/misc.d.ts +9 -0
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +77 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +15 -2
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +89 -15
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +28 -4
- package/dist/cli/handlers/scenario.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.js +8 -7
- package/dist/cli/handlers/search.d.ts +34 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +216 -36
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +55 -1
- package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
- package/dist/cli/mcp/lifecycle-tools.js +3 -2
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.js +4 -3
- package/dist/cli/mcp/workspace-tools.js +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +55 -2
- package/dist/cli/registry.d.ts +19 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cloud-lister/aws.d.ts +35 -0
- package/dist/cloud-lister/aws.d.ts.map +1 -0
- package/dist/cloud-lister/aws.js +203 -0
- package/dist/cloud-lister/azure.d.ts +34 -0
- package/dist/cloud-lister/azure.d.ts.map +1 -0
- package/dist/cloud-lister/azure.js +129 -0
- package/dist/cloud-lister/common.d.ts +40 -0
- package/dist/cloud-lister/common.d.ts.map +1 -0
- package/dist/cloud-lister/common.js +83 -0
- package/dist/cloud-lister/credentials.d.ts +50 -0
- package/dist/cloud-lister/credentials.d.ts.map +1 -0
- package/dist/cloud-lister/credentials.js +86 -0
- package/dist/cloud-lister/fakes.d.ts +27 -0
- package/dist/cloud-lister/fakes.d.ts.map +1 -0
- package/dist/cloud-lister/fakes.js +50 -0
- package/dist/cloud-lister/gcp.d.ts +42 -0
- package/dist/cloud-lister/gcp.d.ts.map +1 -0
- package/dist/cloud-lister/gcp.js +143 -0
- package/dist/cloud-lister/guard.d.ts +49 -0
- package/dist/cloud-lister/guard.d.ts.map +1 -0
- package/dist/cloud-lister/guard.js +103 -0
- package/dist/cloud-lister/ids.d.ts +23 -0
- package/dist/cloud-lister/ids.d.ts.map +1 -0
- package/dist/cloud-lister/ids.js +42 -0
- package/dist/cloud-lister/index.d.ts +67 -0
- package/dist/cloud-lister/index.d.ts.map +1 -0
- package/dist/cloud-lister/index.js +102 -0
- package/dist/cloud-lister/kubernetes.d.ts +64 -0
- package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
- package/dist/cloud-lister/kubernetes.js +276 -0
- package/dist/cloud-lister/markers.d.ts +31 -0
- package/dist/cloud-lister/markers.d.ts.map +1 -0
- package/dist/cloud-lister/markers.js +73 -0
- package/dist/cloud-lister/mask.d.ts +31 -0
- package/dist/cloud-lister/mask.d.ts.map +1 -0
- package/dist/cloud-lister/mask.js +74 -0
- package/dist/cloud-lister/sigv4.d.ts +29 -0
- package/dist/cloud-lister/sigv4.d.ts.map +1 -0
- package/dist/cloud-lister/sigv4.js +54 -0
- package/dist/cloud-lister/transport.d.ts +55 -0
- package/dist/cloud-lister/transport.d.ts.map +1 -0
- package/dist/cloud-lister/transport.js +86 -0
- package/dist/cloud-lister/types.d.ts +121 -0
- package/dist/cloud-lister/types.d.ts.map +1 -0
- package/dist/cloud-lister/types.js +24 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +6 -0
- package/dist/components/capability-plugin.d.ts +9 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability.d.ts +215 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +21 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +58 -14
- package/dist/components/declared-reads.d.ts +38 -0
- package/dist/components/declared-reads.d.ts.map +1 -0
- package/dist/components/declared-reads.js +131 -0
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/deploy-units.js +4 -0
- package/dist/components/driver.d.ts +34 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +66 -3
- package/dist/components/fan-out-run.d.ts +23 -1
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +82 -14
- package/dist/components/fan-out-support.d.ts +6 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +4 -1
- package/dist/components/fan-out.d.ts +28 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/groups.d.ts +229 -0
- package/dist/components/groups.d.ts.map +1 -0
- package/dist/components/groups.js +760 -0
- package/dist/components/index.d.ts +6 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -0
- package/dist/components/plan-shape-conformance.d.ts +48 -0
- package/dist/components/plan-shape-conformance.d.ts.map +1 -0
- package/dist/components/plan-shape-conformance.js +122 -0
- package/dist/components/pr-run.d.ts +41 -4
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +209 -39
- package/dist/components/promote-rounds.d.ts +9 -0
- package/dist/components/promote-rounds.d.ts.map +1 -1
- package/dist/components/promote-rounds.js +23 -2
- package/dist/components/promote.d.ts +17 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +36 -6
- package/dist/components/release-gate.d.ts +23 -1
- package/dist/components/release-gate.d.ts.map +1 -1
- package/dist/components/release-gate.js +97 -33
- package/dist/components/run-groups.d.ts +58 -0
- package/dist/components/run-groups.d.ts.map +1 -0
- package/dist/components/run-groups.js +78 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +6 -1
- package/dist/components/verbs/index.d.ts +1 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +1 -0
- package/dist/components/verbs/member-outputs.d.ts +187 -0
- package/dist/components/verbs/member-outputs.d.ts.map +1 -0
- package/dist/components/verbs/member-outputs.js +218 -0
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -0
- package/dist/deep-observation.d.ts +1 -13
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/deep-observation.js +5 -22
- package/dist/drift-record.d.ts +542 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +886 -0
- package/dist/engine-facts.d.ts +144 -0
- package/dist/engine-facts.d.ts.map +1 -0
- package/dist/engine-facts.js +14 -0
- package/dist/graph-ir.d.ts +153 -2
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +224 -6
- package/dist/graph-refs.d.ts +145 -0
- package/dist/graph-refs.d.ts.map +1 -1
- package/dist/graph-refs.js +305 -0
- package/dist/graph-schema/fixtures/adopt.json +15 -0
- package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/dist/graph-schema/fixtures/index.json +68 -0
- package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/dist/graph-schema/fixtures.d.ts +23 -0
- package/dist/graph-schema/fixtures.d.ts.map +1 -0
- package/dist/graph-schema/fixtures.js +246 -0
- package/dist/graph-schema/generate.d.ts +15 -0
- package/dist/graph-schema/generate.d.ts.map +1 -0
- package/dist/graph-schema/generate.js +267 -0
- package/dist/graph-schema/graph.schema.json +1387 -0
- package/dist/health.schema.json +2 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/instance-nodes.d.ts +159 -0
- package/dist/instance-nodes.d.ts.map +1 -0
- package/dist/instance-nodes.js +231 -0
- package/dist/lexicon.d.ts +72 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/adoption-file.d.ts +56 -0
- package/dist/lifecycle/adoption-file.d.ts.map +1 -0
- package/dist/lifecycle/adoption-file.js +123 -0
- package/dist/lifecycle/cloud-index.d.ts +186 -0
- package/dist/lifecycle/cloud-index.d.ts.map +1 -0
- package/dist/lifecycle/cloud-index.js +618 -0
- package/dist/lifecycle/derive-edges.d.ts +59 -0
- package/dist/lifecycle/derive-edges.d.ts.map +1 -0
- package/dist/lifecycle/derive-edges.js +126 -0
- package/dist/lifecycle/git.d.ts +10 -9
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +7 -1
- package/dist/lifecycle/identity-match.d.ts +133 -0
- package/dist/lifecycle/identity-match.d.ts.map +1 -0
- package/dist/lifecycle/identity-match.js +147 -0
- package/dist/lifecycle/index-diff.d.ts +224 -0
- package/dist/lifecycle/index-diff.d.ts.map +1 -0
- package/dist/lifecycle/index-diff.js +327 -0
- package/dist/lifecycle/index-document.d.ts +130 -0
- package/dist/lifecycle/index-document.d.ts.map +1 -0
- package/dist/lifecycle/index-document.js +189 -0
- package/dist/lifecycle/index-drift.d.ts +121 -0
- package/dist/lifecycle/index-drift.d.ts.map +1 -0
- package/dist/lifecycle/index-drift.js +178 -0
- package/dist/lifecycle/index-import.d.ts +168 -0
- package/dist/lifecycle/index-import.d.ts.map +1 -0
- package/dist/lifecycle/index-import.js +515 -0
- package/dist/lifecycle/index-join.d.ts +129 -0
- package/dist/lifecycle/index-join.d.ts.map +1 -0
- package/dist/lifecycle/index-join.js +248 -0
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/observation-baseline.d.ts +61 -21
- package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
- package/dist/lifecycle/observation-baseline.js +144 -19
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +6 -1
- package/dist/lifecycle/query.d.ts +45 -4
- package/dist/lifecycle/query.d.ts.map +1 -1
- package/dist/lifecycle/query.js +89 -7
- package/dist/lifecycle/replay.d.ts +10 -3
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/replay.js +10 -6
- package/dist/lifecycle/scenario.d.ts +4 -3
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
- package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-fake.js +21 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
- package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store-s3.js +254 -0
- package/dist/lifecycle/snapshot-store.d.ts +170 -0
- package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
- package/dist/lifecycle/snapshot-store.js +832 -0
- package/dist/lifecycle/snapshot.d.ts +11 -0
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.js +18 -5
- package/dist/lifecycle/type-listing.d.ts +148 -0
- package/dist/lifecycle/type-listing.d.ts.map +1 -0
- package/dist/lifecycle/type-listing.js +166 -0
- package/dist/lint/component-checks.d.ts +15 -1
- package/dist/lint/component-checks.d.ts.map +1 -1
- package/dist/lint/component-checks.js +1 -0
- package/dist/lint/presets/relaxed.json +2 -1
- package/dist/lint/presets/strict.json +2 -1
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
- package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
- package/dist/lint/rules/comp/index.d.ts +2 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/comp/index.js +4 -1
- package/dist/observation.d.ts +19 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +18 -1
- package/dist/op/activities/activity-contracts.d.ts +1 -1
- package/dist/op/activities/activity-contracts.js +1 -1
- package/dist/op/activities/change-set.d.ts +6 -4
- package/dist/op/activities/change-set.d.ts.map +1 -1
- package/dist/op/activities/change-set.js +4 -2
- package/dist/op/activity-failure.d.ts +27 -0
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-failure.js +31 -0
- package/dist/op/index.d.ts +2 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +5 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +22 -8
- package/dist/pr-loop.d.ts +56 -1
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +51 -1
- package/dist/pulumi-urn.d.ts +101 -0
- package/dist/pulumi-urn.d.ts.map +1 -0
- package/dist/pulumi-urn.js +131 -0
- package/dist/release-floors.d.ts +23 -0
- package/dist/release-floors.d.ts.map +1 -0
- package/dist/release-floors.js +22 -0
- package/dist/sensitive-key.d.ts +23 -0
- package/dist/sensitive-key.d.ts.map +1 -0
- package/dist/sensitive-key.js +31 -0
- package/dist/wave-runtime.mjs +234 -234
- package/dist/workspace/adoption-report.d.ts +53 -0
- package/dist/workspace/adoption-report.d.ts.map +1 -0
- package/dist/workspace/adoption-report.js +76 -0
- package/dist/workspace/capabilities-contract.d.ts +7 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -1
- package/dist/workspace/capabilities-contract.js +2 -1
- package/dist/workspace/capabilities.schema.json +98 -0
- package/dist/workspace/change-set.schema.json +42 -3
- package/dist/workspace/ci-cloud-identity.d.ts +13 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +30 -1
- package/dist/workspace/compose-graph.d.ts +10 -1
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +6 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +6 -1
- package/dist/workspace/drift-cli.d.ts +81 -0
- package/dist/workspace/drift-cli.d.ts.map +1 -0
- package/dist/workspace/drift-cli.js +317 -0
- package/dist/workspace/drift.schema.json +672 -0
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +3 -0
- package/dist/workspace/graph.schema.json +72 -0
- package/dist/workspace/init.d.ts +5 -1
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/init.js +55 -9
- package/dist/workspace/ledger.schema.json +1 -1
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +2 -0
- package/dist/workspace/pr-report.schema.json +51 -2
- package/dist/workspace/promote-waves.d.ts +112 -0
- package/dist/workspace/promote-waves.d.ts.map +1 -0
- package/dist/workspace/promote-waves.js +290 -0
- package/dist/workspace/promote.d.ts +84 -2
- package/dist/workspace/promote.d.ts.map +1 -1
- package/dist/workspace/promote.js +347 -61
- package/dist/workspace/reason-codes.d.ts +12 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +14 -0
- package/dist/workspace/search-contract.d.ts +3 -3
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/search-contract.js +2 -0
- package/dist/workspace/search.schema.json +103 -5
- package/package.json +14 -1
- package/src/__fixtures__/drift-record/README.md +58 -0
- package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
- package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
- package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
- package/src/__fixtures__/drift-record/component-status.json +91 -0
- package/src/__fixtures__/drift-record/deep-diff.json +259 -0
- package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
- package/src/__fixtures__/drift-record/live-diff.json +125 -0
- package/src/audit/discover.e2e.test.ts +14 -0
- package/src/audit/discover.ts +3 -1
- package/src/behaviour.ts +1 -1
- package/src/capabilities/templates.json +145 -0
- package/src/capabilities/templates.test.ts +212 -0
- package/src/capabilities/templates.ts +204 -0
- package/src/change-set-bundle.test.ts +83 -0
- package/src/change-set-graph.ts +80 -9
- package/src/change-set.test.ts +24 -0
- package/src/change-set.ts +77 -14
- package/src/ci-identity-targets.test.ts +23 -0
- package/src/ci-identity.ts +10 -4
- package/src/cli/commands/import-index.test.ts +117 -0
- package/src/cli/commands/import-index.ts +265 -0
- package/src/cli/commands/lint.ts +4 -2
- package/src/cli/handlers/components.test.ts +9 -0
- package/src/cli/handlers/components.ts +12 -2
- package/src/cli/handlers/fan-out.ts +17 -3
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +26 -4
- package/src/cli/handlers/index-cloud.ts +32 -0
- package/src/cli/handlers/index-diff.test.ts +68 -0
- package/src/cli/handlers/index-diff.ts +146 -0
- package/src/cli/handlers/ledger-root-member.test.ts +161 -0
- package/src/cli/handlers/lifecycle-drift.ts +68 -0
- package/src/cli/handlers/lifecycle.test.ts +33 -11
- package/src/cli/handlers/lifecycle.ts +205 -30
- package/src/cli/handlers/misc.ts +73 -0
- package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
- package/src/cli/handlers/pr.ts +14 -2
- package/src/cli/handlers/promote.test.ts +64 -0
- package/src/cli/handlers/promote.ts +81 -6
- package/src/cli/handlers/query.ts +27 -4
- package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
- package/src/cli/handlers/run-groups-cli.test.ts +229 -0
- package/src/cli/handlers/scenario.test.ts +13 -0
- package/src/cli/handlers/scenario.ts +8 -7
- package/src/cli/handlers/search-index.test.ts +255 -0
- package/src/cli/handlers/search-json.test.ts +1 -1
- package/src/cli/handlers/search-ownership.test.ts +194 -0
- package/src/cli/handlers/search.ts +271 -38
- package/src/cli/main.test.ts +7 -0
- package/src/cli/main.ts +56 -1
- package/src/cli/mcp/lifecycle-tools.ts +3 -2
- package/src/cli/mcp/resource-handlers.ts +4 -3
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/cli/parse-args.ts +40 -2
- package/src/cli/registry.ts +19 -1
- package/src/cloud-lister/aws.test.ts +202 -0
- package/src/cloud-lister/aws.ts +226 -0
- package/src/cloud-lister/azure.test.ts +132 -0
- package/src/cloud-lister/azure.ts +142 -0
- package/src/cloud-lister/common.ts +115 -0
- package/src/cloud-lister/credentials.ts +110 -0
- package/src/cloud-lister/fakes.ts +66 -0
- package/src/cloud-lister/gcp.test.ts +121 -0
- package/src/cloud-lister/gcp.ts +159 -0
- package/src/cloud-lister/guard.test.ts +198 -0
- package/src/cloud-lister/guard.ts +111 -0
- package/src/cloud-lister/ids.ts +54 -0
- package/src/cloud-lister/index.ts +134 -0
- package/src/cloud-lister/kubernetes.test.ts +160 -0
- package/src/cloud-lister/kubernetes.ts +284 -0
- package/src/cloud-lister/markers.ts +74 -0
- package/src/cloud-lister/mask.ts +77 -0
- package/src/cloud-lister/shape.test.ts +81 -0
- package/src/cloud-lister/sigv4.ts +70 -0
- package/src/cloud-lister/transport.ts +118 -0
- package/src/cloud-lister/types.ts +135 -0
- package/src/components/capability-plugin-loader.ts +4 -0
- package/src/components/capability-plugin.ts +10 -0
- package/src/components/capability.ts +229 -0
- package/src/components/cli-support.ts +70 -14
- package/src/components/declared-reads.test.ts +103 -0
- package/src/components/declared-reads.ts +134 -0
- package/src/components/deploy-units.ts +4 -0
- package/src/components/driver.ts +89 -4
- package/src/components/fan-out-run.ts +95 -15
- package/src/components/fan-out-support.ts +10 -1
- package/src/components/fan-out.ts +30 -0
- package/src/components/groups.test.ts +320 -0
- package/src/components/groups.ts +880 -0
- package/src/components/index.ts +42 -0
- package/src/components/plan-shape-conformance.ts +130 -0
- package/src/components/pr-env-run-bundle.test.ts +2 -0
- package/src/components/pr-run.test.ts +516 -3
- package/src/components/pr-run.ts +231 -41
- package/src/components/promote-rounds.ts +25 -2
- package/src/components/promote.ts +55 -5
- package/src/components/registry.test.ts +1 -1
- package/src/components/release-gate.ts +115 -30
- package/src/components/release-groups.test.ts +362 -0
- package/src/components/run-groups.ts +113 -0
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +1 -0
- package/src/components/verbs/member-outputs.test.ts +205 -0
- package/src/components/verbs/member-outputs.ts +308 -0
- package/src/config.ts +44 -0
- package/src/deep-observation.ts +5 -23
- package/src/drift-record.test.ts +596 -0
- package/src/drift-record.ts +1206 -0
- package/src/engine-facts.guard.test.ts +111 -0
- package/src/engine-facts.test.ts +178 -0
- package/src/engine-facts.ts +154 -0
- package/src/gated-waves-bundle.test.ts +2 -0
- package/src/graph-ir-live.test.ts +92 -0
- package/src/graph-ir.ts +317 -7
- package/src/graph-refs.ts +416 -0
- package/src/graph-schema/fixtures/adopt.json +15 -0
- package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
- package/src/graph-schema/fixtures/index.json +68 -0
- package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
- package/src/graph-schema/fixtures.ts +267 -0
- package/src/graph-schema/generate.ts +263 -0
- package/src/graph-schema/graph-schema.test.ts +153 -0
- package/src/graph-schema/graph.schema.json +1387 -0
- package/src/health.schema.json +2 -1
- package/src/identity.ts +2 -0
- package/src/index.ts +2 -0
- package/src/instance-nodes.ts +288 -0
- package/src/lexicon.ts +76 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
- package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
- package/src/lifecycle/adoption-file.test.ts +100 -0
- package/src/lifecycle/adoption-file.ts +154 -0
- package/src/lifecycle/cloud-index.test.ts +517 -0
- package/src/lifecycle/cloud-index.ts +736 -0
- package/src/lifecycle/derive-edges.test.ts +193 -0
- package/src/lifecycle/derive-edges.ts +182 -0
- package/src/lifecycle/git.ts +10 -4
- package/src/lifecycle/identity-match.test.ts +308 -0
- package/src/lifecycle/identity-match.ts +257 -0
- package/src/lifecycle/index-diff.test.ts +198 -0
- package/src/lifecycle/index-diff.ts +476 -0
- package/src/lifecycle/index-document.test.ts +64 -0
- package/src/lifecycle/index-document.ts +299 -0
- package/src/lifecycle/index-drift.test.ts +191 -0
- package/src/lifecycle/index-drift.ts +304 -0
- package/src/lifecycle/index-import.test.ts +255 -0
- package/src/lifecycle/index-import.ts +634 -0
- package/src/lifecycle/index-join.test.ts +250 -0
- package/src/lifecycle/index-join.ts +304 -0
- package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/member-ledger.test.ts +25 -7
- package/src/lifecycle/observation-baseline.test.ts +72 -1
- package/src/lifecycle/observation-baseline.ts +190 -27
- package/src/lifecycle/observe.ts +6 -1
- package/src/lifecycle/query.test.ts +69 -1
- package/src/lifecycle/query.ts +103 -10
- package/src/lifecycle/replay.test.ts +9 -5
- package/src/lifecycle/replay.ts +9 -5
- package/src/lifecycle/scenario.ts +4 -3
- package/src/lifecycle/snapshot-store-fake.ts +29 -0
- package/src/lifecycle/snapshot-store-s3.ts +316 -0
- package/src/lifecycle/snapshot-store.test.ts +385 -0
- package/src/lifecycle/snapshot-store.ts +981 -0
- package/src/lifecycle/snapshot.test.ts +13 -0
- package/src/lifecycle/snapshot.ts +23 -6
- package/src/lifecycle/type-listing.test.ts +166 -0
- package/src/lifecycle/type-listing.ts +309 -0
- package/src/lint/component-checks.ts +17 -1
- package/src/lint/config.test.ts +3 -0
- package/src/lint/presets/relaxed.json +2 -1
- package/src/lint/presets/strict.json +2 -1
- package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
- package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
- package/src/lint/rules/comp/comp.test.ts +2 -1
- package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
- package/src/lint/rules/comp/comp008.test.ts +77 -0
- package/src/lint/rules/comp/index.ts +4 -1
- package/src/observation-edges.test.ts +33 -0
- package/src/observation.ts +36 -1
- package/src/op/activities/activity-contracts.ts +1 -1
- package/src/op/activities/change-set.ts +8 -5
- package/src/op/activity-failure.ts +36 -0
- package/src/op/index.ts +2 -1
- package/src/op/local-executor.test.ts +80 -0
- package/src/op/local-executor.ts +27 -9
- package/src/pr-loop-bundle.test.ts +5 -2
- package/src/pr-loop.test.ts +43 -0
- package/src/pr-loop.ts +111 -2
- package/src/pulumi-urn.ts +151 -0
- package/src/release-floors.ts +22 -0
- package/src/sensitive-key.ts +34 -0
- package/src/workspace/adoption-report.test.ts +61 -0
- package/src/workspace/adoption-report.ts +103 -0
- package/src/workspace/capabilities-contract.test.ts +4 -0
- package/src/workspace/capabilities-contract.ts +9 -1
- package/src/workspace/capabilities.schema.json +98 -0
- package/src/workspace/change-set.schema.json +42 -3
- package/src/workspace/ci-cloud-identity.ts +31 -1
- package/src/workspace/compose-graph.test.ts +12 -0
- package/src/workspace/compose-graph.ts +12 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
- package/src/workspace/conformance/index.ts +6 -1
- package/src/workspace/drift-cli.test.ts +65 -0
- package/src/workspace/drift-cli.ts +365 -0
- package/src/workspace/drift.schema.json +672 -0
- package/src/workspace/graph-cli.ts +5 -0
- package/src/workspace/graph.schema.json +72 -0
- package/src/workspace/init.test.ts +38 -1
- package/src/workspace/init.ts +50 -8
- package/src/workspace/intent-gaps.test.ts +18 -2
- package/src/workspace/ledger.schema.json +1 -1
- package/src/workspace/nested-graph.ts +1 -0
- package/src/workspace/pr-report.schema.json +51 -2
- package/src/workspace/promote-waves.test.ts +296 -0
- package/src/workspace/promote-waves.ts +340 -0
- package/src/workspace/promote.test.ts +158 -4
- package/src/workspace/promote.ts +398 -57
- package/src/workspace/reason-codes.test.ts +4 -1
- package/src/workspace/reason-codes.ts +14 -0
- package/src/workspace/search-contract.ts +2 -0
- package/src/workspace/search.schema.json +103 -5
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The index is never load-bearing (#4519, rule 1).
|
|
3
|
+
*
|
|
4
|
+
* Every snapshot and every other index is derived and safe to lose: removing
|
|
5
|
+
* the store costs history and the cost of observing again, never a decision.
|
|
6
|
+
* Two checks hold that:
|
|
7
|
+
*
|
|
8
|
+
* 1. Behaviour. `buildChangeSet`, which `lifecycle plan` feeds from the store
|
|
9
|
+
* (`observedThen`), proposes the same create, delete, adopt, runtime and
|
|
10
|
+
* unobserved entries, with the same ownership, whether the last snapshot is
|
|
11
|
+
* there, absent (store `none`), or says something else entirely. The only
|
|
12
|
+
* thing a snapshot may move is evidence: an `update` row's deltas are drift
|
|
13
|
+
* since the last read, and with no last read there is none to show.
|
|
14
|
+
* A BREAK that lets the snapshot decide a delete is caught by the same
|
|
15
|
+
* check, which the second test proves.
|
|
16
|
+
* 2. Imports. The modules that decide what to mutate (the change set, the
|
|
17
|
+
* unobserved gate, teardown, apply, the gate and its decision) do not load
|
|
18
|
+
* the store, the snapshot writer or the replay, so they cannot read an index.
|
|
19
|
+
*/
|
|
20
|
+
import { describe, expect, test } from "vitest";
|
|
21
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
22
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { buildChangeSet, type ChangeSet } from "./change-set";
|
|
25
|
+
import { createSnapshotStore } from "./snapshot-store";
|
|
26
|
+
import { fakeSnapshotStore } from "./snapshot-store-fake";
|
|
27
|
+
import type { DiffLiveInput } from "./live-diff";
|
|
28
|
+
import type { ResourceMetadata } from "../lexicon";
|
|
29
|
+
import type { SnapshotStore } from "./snapshot-store";
|
|
30
|
+
|
|
31
|
+
const meta = (over: Partial<ResourceMetadata> = {}): ResourceMetadata => ({ type: "Fake::Resource", status: "OK", ...over });
|
|
32
|
+
|
|
33
|
+
type Build = (env: string, input: DiffLiveInput) => ChangeSet;
|
|
34
|
+
|
|
35
|
+
/** The live side of each case. The snapshot side is varied below. */
|
|
36
|
+
const cases: Array<{ name: string; input: Omit<DiffLiveInput, "observedThen"> }> = [
|
|
37
|
+
{
|
|
38
|
+
name: "declared and live, declared and absent, owned orphan, foreign orphan",
|
|
39
|
+
input: {
|
|
40
|
+
declared: new Set(["kept", "missing"]),
|
|
41
|
+
observedNow: {
|
|
42
|
+
kept: meta({ ownership: "owned" }),
|
|
43
|
+
orphanOwned: meta({ ownership: "owned" }),
|
|
44
|
+
orphanForeign: meta({ ownership: "foreign" }),
|
|
45
|
+
orphanUnknown: meta(),
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "a runtime child and an unobserved declaration",
|
|
51
|
+
input: {
|
|
52
|
+
declared: new Set(["owner", "hole"]),
|
|
53
|
+
observedNow: {
|
|
54
|
+
owner: meta({ ownership: "owned" }),
|
|
55
|
+
child: meta({ ownerChain: { root: "declared", entity: "owner" } } as Partial<ResourceMetadata>),
|
|
56
|
+
},
|
|
57
|
+
unobserved: { hole: { type: "Fake::Resource", reason: "no-credentials" } },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/** Snapshots that disagree with the live read in every way a snapshot can. */
|
|
63
|
+
const snapshotsFor = (input: Omit<DiffLiveInput, "observedThen">): Array<Record<string, ResourceMetadata> | undefined> => {
|
|
64
|
+
const names = [...new Set([...input.declared, ...Object.keys(input.observedNow), "gone"])];
|
|
65
|
+
return [
|
|
66
|
+
undefined,
|
|
67
|
+
{},
|
|
68
|
+
Object.fromEntries(names.map((n) => [n, meta({ ownership: "owned", status: "STALE" })])),
|
|
69
|
+
Object.fromEntries(names.map((n) => [n, meta({ ownership: "foreign", status: "OTHER", physicalId: `old-${n}` })])),
|
|
70
|
+
];
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const DECISIONS = new Set(["create", "delete", "adopt", "runtime", "unobserved", "effect"]);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Every way `build` lets a snapshot change a decision, as text, empty when
|
|
77
|
+
* none does. A name only the snapshot knows is never a decision.
|
|
78
|
+
*/
|
|
79
|
+
function loadBearing(build: Build): string[] {
|
|
80
|
+
const found: string[] = [];
|
|
81
|
+
for (const c of cases) {
|
|
82
|
+
const without = build("prod", { ...c.input, observedThen: undefined });
|
|
83
|
+
const decided = new Map(without.entries.map((e) => [e.name, e]));
|
|
84
|
+
for (const then of snapshotsFor(c.input)) {
|
|
85
|
+
const withSnap = build("prod", { ...c.input, observedThen: then });
|
|
86
|
+
for (const e of withSnap.entries) {
|
|
87
|
+
const base = decided.get(e.name);
|
|
88
|
+
if (!base) {
|
|
89
|
+
if (e.action !== "noop") found.push(`${c.name}: ${e.name} exists only in the snapshot and became ${e.action}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (e.ownership !== base.ownership) found.push(`${c.name}: ${e.name} ownership ${base.ownership} -> ${e.ownership}`);
|
|
93
|
+
const decisionMoved = (DECISIONS.has(e.action) || DECISIONS.has(base.action)) && e.action !== base.action;
|
|
94
|
+
if (decisionMoved) found.push(`${c.name}: ${e.name} ${base.action} -> ${e.action}`);
|
|
95
|
+
// Drift since the last read is the one thing a snapshot adds, and only between update and noop.
|
|
96
|
+
if (!decisionMoved && e.action !== base.action && !(base.action === "noop" && e.action === "update")) {
|
|
97
|
+
found.push(`${c.name}: ${e.name} ${base.action} -> ${e.action}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return found;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
describe("the index is never load-bearing", () => {
|
|
106
|
+
test("no snapshot, an empty one or a contradicting one changes a decision", () => {
|
|
107
|
+
expect(loadBearing(buildChangeSet)).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("a BREAK that lets the snapshot decide a delete is caught", () => {
|
|
111
|
+
// The break: an undeclared resource the last snapshot called owned is deleted.
|
|
112
|
+
const broken: Build = (env, input) => {
|
|
113
|
+
const cs = buildChangeSet(env, input);
|
|
114
|
+
return {
|
|
115
|
+
...cs,
|
|
116
|
+
entries: cs.entries.map((e) =>
|
|
117
|
+
e.action === "adopt" && input.observedThen?.[e.name]?.ownership === "owned" ? { ...e, action: "delete" as const, ownership: "owned" as const } : e,
|
|
118
|
+
),
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
expect(loadBearing(broken).some((f) => f.includes("adopt -> delete"))).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("lifecycle plan's read through the store: store none and a populated store decide alike", async () => {
|
|
125
|
+
const input = cases[0].input;
|
|
126
|
+
const populated: SnapshotStore = fakeSnapshotStore({
|
|
127
|
+
readLatest: async () => JSON.stringify({ resources: snapshotsFor(input)[2] }),
|
|
128
|
+
});
|
|
129
|
+
const none = createSnapshotStore({ store: "none" });
|
|
130
|
+
// What runLifecyclePlan does with the store's answer.
|
|
131
|
+
const plan = async (store: SnapshotStore) => {
|
|
132
|
+
const content = await store.readLatest("prod", "fake");
|
|
133
|
+
const observedThen = content ? (JSON.parse(content) as { resources: Record<string, ResourceMetadata> }).resources : undefined;
|
|
134
|
+
return buildChangeSet("prod", { ...input, observedThen });
|
|
135
|
+
};
|
|
136
|
+
const decisions = (cs: ChangeSet) => cs.entries.filter((e) => DECISIONS.has(e.action)).map((e) => `${e.name}:${e.action}:${e.ownership}`);
|
|
137
|
+
expect(decisions(await plan(populated))).toEqual(decisions(await plan(none)));
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("the modules that decide what to mutate never load the index store", () => {
|
|
142
|
+
const SRC = fileURLToPath(new URL("..", import.meta.url));
|
|
143
|
+
const FORBIDDEN = /lifecycle\/(snapshot-store|snapshot-store-s3|snapshot|replay)\.ts$/;
|
|
144
|
+
const DECIDERS = [
|
|
145
|
+
"lifecycle/change-set.ts",
|
|
146
|
+
"lifecycle/unobserved-gate.ts",
|
|
147
|
+
"lifecycle/teardown.ts",
|
|
148
|
+
"lifecycle/green-apply.ts",
|
|
149
|
+
"apply.ts",
|
|
150
|
+
"op/gate.ts",
|
|
151
|
+
"gate-decision.ts",
|
|
152
|
+
];
|
|
153
|
+
// Runtime imports only: `import type`, `export type` and `import("...")` in a type position load nothing.
|
|
154
|
+
const STATIC = /(?:^|\n)\s*(?:import|export)\s+(?!type\s)[^;]*?from\s+["'](\.[^"']+)["']/g;
|
|
155
|
+
const DYNAMIC = /await\s+import\(\s*["'](\.[^"']+)["']\s*\)/g;
|
|
156
|
+
|
|
157
|
+
function resolveSpec(from: string, spec: string): string | null {
|
|
158
|
+
const base = resolve(dirname(from), spec);
|
|
159
|
+
for (const c of [base, `${base}.ts`, join(base, "index.ts"), base.replace(/\.js$/, ".ts")]) {
|
|
160
|
+
if (c.endsWith(".ts") && existsSync(c)) return c;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
test.each(DECIDERS)("%s", (entry) => {
|
|
166
|
+
const start = join(SRC, entry);
|
|
167
|
+
const parent = new Map<string, string | null>([[start, null]]);
|
|
168
|
+
const queue = [start];
|
|
169
|
+
while (queue.length > 0) {
|
|
170
|
+
const file = queue.shift()!;
|
|
171
|
+
const text = readFileSync(file, "utf8");
|
|
172
|
+
for (const m of [...text.matchAll(STATIC), ...text.matchAll(DYNAMIC)]) {
|
|
173
|
+
const target = resolveSpec(file, m[1]);
|
|
174
|
+
if (target && !parent.has(target)) {
|
|
175
|
+
parent.set(target, file);
|
|
176
|
+
queue.push(target);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const reached = [...parent.keys()].filter((f) => FORBIDDEN.test(f)).map((f) => {
|
|
181
|
+
const chain: string[] = [];
|
|
182
|
+
for (let c: string | null | undefined = f; c; c = parent.get(c)) chain.push(relative(SRC, c));
|
|
183
|
+
return chain.reverse().join(" -> ");
|
|
184
|
+
});
|
|
185
|
+
expect(reached).toEqual([]);
|
|
186
|
+
});
|
|
187
|
+
});
|
package/src/lifecycle/index.ts
CHANGED
|
@@ -2,6 +2,10 @@ export * from "./types";
|
|
|
2
2
|
export * from "./git";
|
|
3
3
|
export * from "./digest";
|
|
4
4
|
export * from "./snapshot";
|
|
5
|
+
export * from "./snapshot-store";
|
|
6
|
+
export * from "./snapshot-store-s3";
|
|
7
|
+
export * from "./index-document";
|
|
8
|
+
export * from "./index-diff";
|
|
5
9
|
export * from "./live-diff";
|
|
6
10
|
export * from "./deep-diff";
|
|
7
11
|
export * from "./deep-observe";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { afterAll, beforeEach, describe, expect, test } from "vitest";
|
|
12
12
|
import { spawnSync } from "node:child_process";
|
|
13
|
-
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { tmpdir } from "node:os";
|
|
15
15
|
import { dirname, join } from "node:path";
|
|
16
16
|
import { clearMemberLedgerCache, memberLedgerPrefix, MemberLedgerError, resolveMemberLedger } from "./member-ledger";
|
|
@@ -192,19 +192,37 @@ describe("every lifecycle store writes under the member path (#2538)", () => {
|
|
|
192
192
|
const web = join(root, "services/web");
|
|
193
193
|
await writeSnapshot("prod", "k8s", '{"from":"api"}', { cwd: api });
|
|
194
194
|
await writeSnapshot("prod", "k8s", '{"from":"web"}', { cwd: web });
|
|
195
|
-
|
|
195
|
+
// Written from a subdirectory of the member: the baseline still lands under the member.
|
|
196
|
+
await writeObservationBaseline(emptyBaseline("prod"), { cwd: join(api, "src") });
|
|
196
197
|
|
|
197
198
|
const files = branchFiles(root);
|
|
198
|
-
expect(files).
|
|
199
|
-
expect(files).toContain("_members/web/prod/k8s.json");
|
|
200
|
-
expect(files.filter((f) => f.startsWith("_members/api/prod/") && f !== "_members/api/prod/k8s.json")).toHaveLength(1);
|
|
201
|
-
expect(files.every((f) => f.startsWith("_members/"))).toBe(true);
|
|
199
|
+
expect(files).toEqual(["_members/api/prod/k8s.json", "_members/web/prod/k8s.json"]);
|
|
202
200
|
expect([...(await readEnvironmentSnapshots("prod", { cwd: web })).values()]).toEqual(['{"from":"web"}']);
|
|
201
|
+
// The baseline's home is a file under the project since #4519
|
|
202
|
+
// (.chant/observation-baseline/<env>.json), and the project is the member's directory.
|
|
203
|
+
expect(existsSync(join(api, ".chant/observation-baseline/prod.json"))).toBe(true);
|
|
204
|
+
expect(existsSync(join(api, "src/.chant"))).toBe(false);
|
|
205
|
+
expect(existsSync(join(web, ".chant"))).toBe(false);
|
|
206
|
+
expect(existsSync(join(root, ".chant"))).toBe(false);
|
|
203
207
|
expect(await readObservationBaseline("prod", { cwd: api })).toMatchObject({ environment: "prod" });
|
|
204
208
|
expect(await readObservationBaseline("prod", { cwd: web })).toBeNull();
|
|
209
|
+
expect(await readObservationBaseline("prod", { cwd: root })).toBeNull();
|
|
205
210
|
// A member's history lists only the commits that touched its ledger.
|
|
206
211
|
expect(await listSnapshots({ cwd: web })).toHaveLength(1);
|
|
207
|
-
expect(await listSnapshots({ cwd: root })).toHaveLength(
|
|
212
|
+
expect(await listSnapshots({ cwd: root })).toHaveLength(2);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("an observation baseline on the git-ref home is under the member path", async () => {
|
|
216
|
+
const root = workspace();
|
|
217
|
+
const api = join(root, "services/api");
|
|
218
|
+
const web = join(root, "services/web");
|
|
219
|
+
const store = { store: "git-ref" as const };
|
|
220
|
+
await writeObservationBaseline(emptyBaseline("prod"), { cwd: api, store });
|
|
221
|
+
const onRef = git(["ls-tree", "-r", "--name-only", "refs/chant/observation-baseline"], root).split("\n").filter(Boolean);
|
|
222
|
+
expect(onRef).toEqual(["_members/api/prod.json"]);
|
|
223
|
+
expect(await readObservationBaseline("prod", { cwd: api, store })).toMatchObject({ environment: "prod" });
|
|
224
|
+
expect(await readObservationBaseline("prod", { cwd: web, store })).toBeNull();
|
|
225
|
+
expect(await readObservationBaseline("prod", { cwd: root, store })).toBeNull();
|
|
208
226
|
});
|
|
209
227
|
|
|
210
228
|
test("runs and converge records", async () => {
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import { describe, test, expect } from "vitest";
|
|
1
|
+
import { afterEach, describe, test, expect } from "vitest";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync as readFile, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { writeBlobToPath } from "./git";
|
|
2
7
|
import {
|
|
8
|
+
observationBaselineStoreConfig,
|
|
9
|
+
readObservationBaseline,
|
|
10
|
+
saveObservationBaseline,
|
|
11
|
+
OBSERVATION_BASELINE_FILE,
|
|
3
12
|
acceptDeviations,
|
|
4
13
|
acceptedDeviation,
|
|
5
14
|
baselineForLexicon,
|
|
@@ -143,3 +152,65 @@ describe("acceptDeviations refuses effect receipts (#1833)", () => {
|
|
|
143
152
|
expect(baselineForLexicon(b, "aws").Role.accepted).toHaveLength(1);
|
|
144
153
|
});
|
|
145
154
|
});
|
|
155
|
+
|
|
156
|
+
// ── Where it lives (#4519) ──────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
describe("the baseline's home", () => {
|
|
159
|
+
const dirs: string[] = [];
|
|
160
|
+
afterEach(() => {
|
|
161
|
+
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
|
|
162
|
+
});
|
|
163
|
+
const repo = (): string => {
|
|
164
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "baseline-home-")));
|
|
165
|
+
dirs.push(dir);
|
|
166
|
+
const git = (args: string[]) => execFileSync("git", args, { cwd: dir, encoding: "utf8" });
|
|
167
|
+
git(["init", "-q", "-b", "main"]);
|
|
168
|
+
git(["config", "user.email", "test@chant.dev"]);
|
|
169
|
+
git(["config", "user.name", "Test"]);
|
|
170
|
+
writeFileSync(join(dir, "README.md"), "fixture\n");
|
|
171
|
+
git(["add", "README.md"]);
|
|
172
|
+
git(["commit", "-q", "-m", "init"]);
|
|
173
|
+
return dir;
|
|
174
|
+
};
|
|
175
|
+
const accepted = () =>
|
|
176
|
+
acceptDeviations(emptyBaseline("prod"), "aws", [{ entity: "Assets", path: "Tags[0].Value", value: "platform" }], { now: "2026-10-26T00:00:00.000Z" });
|
|
177
|
+
|
|
178
|
+
test("the default is a file under .chant/observation-baseline/, written for review and never on the orphan branch", async () => {
|
|
179
|
+
const cwd = repo();
|
|
180
|
+
const saved = await saveObservationBaseline(accepted(), { cwd, config: {} });
|
|
181
|
+
expect(saved).toMatchObject({ where: join(".chant", "observation-baseline", "prod.json"), published: "file" });
|
|
182
|
+
const file = join(cwd, ".chant", "observation-baseline", "prod.json");
|
|
183
|
+
expect(parseBaseline(readFile(file, "utf8"))).toEqual(accepted());
|
|
184
|
+
expect(await readObservationBaseline("prod", { cwd, config: {} })).toEqual(accepted());
|
|
185
|
+
expect(() => execFileSync("git", ["rev-parse", "--verify", "-q", "refs/heads/chant/lifecycle"], { cwd })).toThrow();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("a baseline written on chant/lifecycle before #4519 is still read when no file holds one", async () => {
|
|
189
|
+
const cwd = repo();
|
|
190
|
+
await writeBlobToPath("prod", OBSERVATION_BASELINE_FILE, serializeBaseline(accepted()), "legacy", { cwd });
|
|
191
|
+
expect(await readObservationBaseline("prod", { cwd, config: {} })).toEqual(accepted());
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("none keeps nothing and reads nothing", async () => {
|
|
195
|
+
const cwd = repo();
|
|
196
|
+
const store = { store: "none" as const };
|
|
197
|
+
expect((await saveObservationBaseline(accepted(), { cwd, store })).published).toBe("none");
|
|
198
|
+
expect(existsSync(join(cwd, ".chant"))).toBe(false);
|
|
199
|
+
expect(await readObservationBaseline("prod", { cwd, store })).toBeNull();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("git-ref writes refs/chant/observation-baseline and reads it back", async () => {
|
|
203
|
+
const cwd = repo();
|
|
204
|
+
const store = { store: "git-ref" as const };
|
|
205
|
+
const saved = await saveObservationBaseline(accepted(), { cwd, store });
|
|
206
|
+
expect(saved.where).toBe("refs/chant/observation-baseline:prod.json");
|
|
207
|
+
expect(saved.published).toBe("local");
|
|
208
|
+
expect(await readObservationBaseline("prod", { cwd, store })).toEqual(accepted());
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("the environment overrides chant.config.ts", () => {
|
|
212
|
+
expect(observationBaselineStoreConfig(undefined, {})).toEqual({ store: "file" });
|
|
213
|
+
expect(observationBaselineStoreConfig({ store: "git-ref" }, { CHANT_OBSERVATION_BASELINE_STORE: "none" })).toEqual({ store: "none" });
|
|
214
|
+
expect(() => observationBaselineStoreConfig({ store: "branch" }, {})).toThrow(/not one of/);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
@@ -23,25 +23,40 @@
|
|
|
23
23
|
*
|
|
24
24
|
* ## Where it lives
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* everything chant records *about* an environment rather than *for* it.
|
|
26
|
+
* A file in the repository, `.chant/observation-baseline/<env>.json` under
|
|
27
|
+
* the project (#4519). An acceptance is then a commit in an ordinary pull
|
|
28
|
+
* request, reviewed with the change that needed it. It is not derivable and
|
|
29
|
+
* not critical, and it is meant to be reviewed: that is the rule that puts a
|
|
30
|
+
* record here rather than in the index store.
|
|
32
31
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* Other homes, through `observationBaseline` in chant.config.ts or
|
|
33
|
+
* `CHANT_OBSERVATION_BASELINE_STORE`: `git-ref` (`refs/chant/observation-baseline`),
|
|
34
|
+
* `s3` (`<prefix>/observation-baseline/<env>.json`, with the s3 settings of
|
|
35
|
+
* `observationBaseline.s3`, else of `snapshots`), or `none`.
|
|
36
|
+
*
|
|
37
|
+
* A baseline written before #4519, `<environment>/observation-baseline.json`
|
|
38
|
+
* on the `chant/lifecycle` orphan branch, is still read when the configured
|
|
39
|
+
* home has none. It is never written there again.
|
|
40
|
+
*
|
|
41
|
+
* The parse/serialize/update half below is pure and storage-free.
|
|
36
42
|
*/
|
|
37
43
|
|
|
38
|
-
import {
|
|
44
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
45
|
+
import { dirname, join, resolve } from "node:path";
|
|
46
|
+
import { ledgerDir, readBlobFromPath } from "./git";
|
|
47
|
+
import { encodeIndexSegment } from "./index-document";
|
|
39
48
|
import { sortedJsonReplacer } from "../utils";
|
|
40
49
|
import { EFFECT_RECEIPT_ENTITY_TYPE } from "../effect-receipt";
|
|
41
50
|
|
|
42
|
-
/** The file name under `<environment>/` on the orphan branch. */
|
|
51
|
+
/** The file name under `<environment>/` on the orphan branch, where a baseline written before #4519 is read from. */
|
|
43
52
|
export const OBSERVATION_BASELINE_FILE = "observation-baseline.json";
|
|
44
53
|
|
|
54
|
+
/** The directory, relative to the project, that holds one baseline file per environment (#4519). */
|
|
55
|
+
export const OBSERVATION_BASELINE_DIR = ".chant/observation-baseline";
|
|
56
|
+
|
|
57
|
+
/** The ref a `git-ref` baseline is written to. */
|
|
58
|
+
export const OBSERVATION_BASELINE_REF = "refs/chant/observation-baseline";
|
|
59
|
+
|
|
45
60
|
/** One deviation somebody accepted, bound to the value they accepted. */
|
|
46
61
|
export interface AcceptedDeviation {
|
|
47
62
|
/** Property path within the entity's normalized tree (`Tags[0].Value`, `Policy.Statement[1].Effect`). */
|
|
@@ -219,31 +234,179 @@ export function countAccepted(baseline: ObservationBaseline | null | undefined):
|
|
|
219
234
|
return n;
|
|
220
235
|
}
|
|
221
236
|
|
|
222
|
-
// ── Storage
|
|
237
|
+
// ── Storage ─────────────────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
export type ObservationBaselineStoreKind = "file" | "git-ref" | "s3" | "none";
|
|
240
|
+
|
|
241
|
+
/** `observationBaseline` in chant.config.ts. */
|
|
242
|
+
export interface ObservationBaselineStoreConfig {
|
|
243
|
+
store: ObservationBaselineStoreKind;
|
|
244
|
+
/** `file`: the directory, relative to the project. Default {@link OBSERVATION_BASELINE_DIR}. */
|
|
245
|
+
dir?: string;
|
|
246
|
+
/** `git-ref`: the ref. Default {@link OBSERVATION_BASELINE_REF}. */
|
|
247
|
+
ref?: string;
|
|
248
|
+
/** `s3`: where. Default: the `snapshots` store's s3 settings. */
|
|
249
|
+
s3?: import("./snapshot-store-s3").S3StoreSettings;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const BASELINE_KINDS: readonly ObservationBaselineStoreKind[] = ["file", "git-ref", "s3", "none"];
|
|
253
|
+
|
|
254
|
+
/** Where to read and write a baseline: the project directory, and its config when the caller has it loaded. */
|
|
255
|
+
export interface ObservationBaselineLocation {
|
|
256
|
+
cwd?: string;
|
|
257
|
+
/** The project's loaded chant.config. Loaded from `cwd` upward when absent. */
|
|
258
|
+
config?: { observationBaseline?: unknown; snapshots?: unknown };
|
|
259
|
+
/** Overrides the configured home. */
|
|
260
|
+
store?: ObservationBaselineStoreConfig;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** The home in effect: `CHANT_OBSERVATION_BASELINE_STORE` over `observationBaseline` over `file`. */
|
|
264
|
+
export function observationBaselineStoreConfig(
|
|
265
|
+
configured?: unknown,
|
|
266
|
+
env: Record<string, string | undefined> = process.env,
|
|
267
|
+
): ObservationBaselineStoreConfig {
|
|
268
|
+
const base = (configured && typeof configured === "object" ? configured : {}) as Partial<ObservationBaselineStoreConfig>;
|
|
269
|
+
const kind = (env.CHANT_OBSERVATION_BASELINE_STORE?.trim() || base.store || "file") as ObservationBaselineStoreKind;
|
|
270
|
+
if (!BASELINE_KINDS.includes(kind)) {
|
|
271
|
+
throw new Error(`observation baseline store "${kind}" is not one of ${BASELINE_KINDS.join(", ")}`);
|
|
272
|
+
}
|
|
273
|
+
return { ...(base.store === kind ? base : {}), store: kind };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function resolveLocation(
|
|
277
|
+
opts?: ObservationBaselineLocation,
|
|
278
|
+
): Promise<{ cwd: string; projectDir: string; store: ObservationBaselineStoreConfig; snapshots?: unknown }> {
|
|
279
|
+
const cwd = resolve(opts?.cwd ?? process.cwd());
|
|
280
|
+
const { findProjectConfigPastFragments, loadChantConfigUpward } = await import("../config");
|
|
281
|
+
let config: ObservationBaselineLocation["config"] = opts?.config;
|
|
282
|
+
if (!opts?.store && config === undefined) {
|
|
283
|
+
try {
|
|
284
|
+
config = (await loadChantConfigUpward(cwd)).config;
|
|
285
|
+
} catch {
|
|
286
|
+
config = undefined;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// The `file` home is relative to the project, not to wherever the command
|
|
290
|
+
// ran: a run from a member's subdirectory writes under that member's own
|
|
291
|
+
// directory, as every other store writes under its member path (#2538).
|
|
292
|
+
const projectDir = findProjectConfigPastFragments(cwd).dir;
|
|
293
|
+
return { cwd, projectDir, store: opts?.store ?? observationBaselineStoreConfig(config?.observationBaseline), snapshots: config?.snapshots };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** The `file` home's path for one environment, under the project directory. */
|
|
297
|
+
function baselineFile(projectDir: string, store: ObservationBaselineStoreConfig, env: string): string {
|
|
298
|
+
return join(projectDir, store.dir ?? OBSERVATION_BASELINE_DIR, `${env}.json`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function s3For(store: ObservationBaselineStoreConfig, snapshots: unknown) {
|
|
302
|
+
const { S3Medium } = await import("./snapshot-store-s3");
|
|
303
|
+
const { snapshotStoreConfig } = await import("./snapshot-store");
|
|
304
|
+
let settings = store.s3;
|
|
305
|
+
if (!settings) {
|
|
306
|
+
const snap = snapshotStoreConfig(snapshots);
|
|
307
|
+
if (snap.store !== "s3") throw new Error("the s3 observation baseline store needs observationBaseline.s3, or a snapshots store that is s3");
|
|
308
|
+
const { store: _s, ...rest } = snap;
|
|
309
|
+
settings = rest;
|
|
310
|
+
}
|
|
311
|
+
return new S3Medium(settings);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Where a baseline for `environment` lives in the configured home, for messages. */
|
|
315
|
+
export async function observationBaselineWhere(environment: string, opts?: ObservationBaselineLocation): Promise<string> {
|
|
316
|
+
const { cwd, store, snapshots } = await resolveLocation(opts);
|
|
317
|
+
switch (store.store) {
|
|
318
|
+
case "file":
|
|
319
|
+
return join(store.dir ?? OBSERVATION_BASELINE_DIR, `${encodeIndexSegment(environment)}.json`);
|
|
320
|
+
case "git-ref":
|
|
321
|
+
return `${store.ref ?? OBSERVATION_BASELINE_REF}:${await ledgerDir(encodeIndexSegment(environment), { cwd })}.json`;
|
|
322
|
+
case "s3":
|
|
323
|
+
return `${(await s3For(store, snapshots)).where}/observation-baseline/${await ledgerDir(encodeIndexSegment(environment), { cwd })}.json`;
|
|
324
|
+
case "none":
|
|
325
|
+
return "nowhere (the observation baseline store is none)";
|
|
326
|
+
}
|
|
327
|
+
}
|
|
223
328
|
|
|
224
329
|
/**
|
|
225
|
-
* Read the accepted baseline for an environment. Returns `null` when
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
330
|
+
* Read the accepted baseline for an environment. Returns `null` when there is
|
|
331
|
+
* none, the normal state before anyone runs `--update-baseline`. When the
|
|
332
|
+
* configured home has none, a baseline written before #4519 on the
|
|
333
|
+
* `chant/lifecycle` branch is read instead.
|
|
229
334
|
*/
|
|
230
335
|
export async function readObservationBaseline(
|
|
231
336
|
environment: string,
|
|
232
|
-
opts?:
|
|
337
|
+
opts?: ObservationBaselineLocation,
|
|
233
338
|
): Promise<ObservationBaseline | null> {
|
|
234
|
-
|
|
339
|
+
const { cwd, projectDir, store, snapshots } = await resolveLocation(opts);
|
|
340
|
+
let text: string | null = null;
|
|
341
|
+
switch (store.store) {
|
|
342
|
+
case "none":
|
|
343
|
+
return null;
|
|
344
|
+
case "file":
|
|
345
|
+
try {
|
|
346
|
+
text = readFileSync(baselineFile(projectDir, store, encodeIndexSegment(environment)), "utf8");
|
|
347
|
+
} catch {
|
|
348
|
+
text = null;
|
|
349
|
+
}
|
|
350
|
+
break;
|
|
351
|
+
case "git-ref": {
|
|
352
|
+
const { getRuntime } = await import("../runtime-adapter");
|
|
353
|
+
const path = `${await ledgerDir(encodeIndexSegment(environment), { cwd })}.json`;
|
|
354
|
+
const r = await getRuntime().spawn(["git", "cat-file", "blob", `${store.ref ?? OBSERVATION_BASELINE_REF}:${path}`], { cwd });
|
|
355
|
+
text = r.exitCode === 0 ? r.stdout : null;
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case "s3":
|
|
359
|
+
text = await (await s3For(store, snapshots)).get(`observation-baseline/${await ledgerDir(encodeIndexSegment(environment), { cwd })}.json`);
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
return parseBaseline(text ?? (await readBlobFromPath(environment, OBSERVATION_BASELINE_FILE, { cwd })));
|
|
235
363
|
}
|
|
236
364
|
|
|
237
|
-
/**
|
|
365
|
+
/** What {@link saveObservationBaseline} did. `file` means written in the working tree, for the caller to commit. */
|
|
366
|
+
export interface SavedObservationBaseline {
|
|
367
|
+
where: string;
|
|
368
|
+
published: "file" | "pushed" | "local" | "failed" | "none";
|
|
369
|
+
detail?: string;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Write the accepted baseline to its home, and push it when the home is a ref. */
|
|
373
|
+
export async function saveObservationBaseline(
|
|
374
|
+
baseline: ObservationBaseline,
|
|
375
|
+
opts?: ObservationBaselineLocation,
|
|
376
|
+
): Promise<SavedObservationBaseline> {
|
|
377
|
+
const { cwd, projectDir, store, snapshots } = await resolveLocation(opts);
|
|
378
|
+
const where = await observationBaselineWhere(baseline.environment, { cwd, store, ...(snapshots !== undefined ? { config: { snapshots } } : {}) });
|
|
379
|
+
const content = serializeBaseline(baseline);
|
|
380
|
+
const env = encodeIndexSegment(baseline.environment);
|
|
381
|
+
switch (store.store) {
|
|
382
|
+
case "none":
|
|
383
|
+
return { where, published: "none" };
|
|
384
|
+
case "file": {
|
|
385
|
+
const file = baselineFile(projectDir, store, env);
|
|
386
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
387
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
388
|
+
writeFileSync(tmp, content);
|
|
389
|
+
renameSync(tmp, file);
|
|
390
|
+
return { where, published: "file" };
|
|
391
|
+
}
|
|
392
|
+
case "git-ref": {
|
|
393
|
+
const { commitFilesToRef, publishRef } = await import("./snapshot-store");
|
|
394
|
+
const ref = store.ref ?? OBSERVATION_BASELINE_REF;
|
|
395
|
+
const path = `${await ledgerDir(env, { cwd })}.json`;
|
|
396
|
+
await commitFilesToRef(ref, [{ path, content }], "Accepted observation baseline", { cwd });
|
|
397
|
+
const pushed = await publishRef(ref, new Map([[path, content]]), "Accepted observation baseline", { cwd });
|
|
398
|
+
return { where, published: pushed.status === "pushed" ? "pushed" : pushed.status === "local" ? "local" : "failed", ...(pushed.detail ? { detail: pushed.detail } : {}) };
|
|
399
|
+
}
|
|
400
|
+
case "s3":
|
|
401
|
+
await (await s3For(store, snapshots)).put(`observation-baseline/${await ledgerDir(env, { cwd })}.json`, content);
|
|
402
|
+
return { where, published: "pushed" };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** Write the accepted baseline to its home. Returns where it went. */
|
|
238
407
|
export async function writeObservationBaseline(
|
|
239
408
|
baseline: ObservationBaseline,
|
|
240
|
-
opts?:
|
|
409
|
+
opts?: ObservationBaselineLocation,
|
|
241
410
|
): Promise<string> {
|
|
242
|
-
return
|
|
243
|
-
baseline.environment,
|
|
244
|
-
OBSERVATION_BASELINE_FILE,
|
|
245
|
-
serializeBaseline(baseline),
|
|
246
|
-
"Accepted observation baseline",
|
|
247
|
-
opts,
|
|
248
|
-
);
|
|
411
|
+
return (await saveObservationBaseline(baseline, opts)).where;
|
|
249
412
|
}
|
package/src/lifecycle/observe.ts
CHANGED
|
@@ -96,6 +96,8 @@ function qualifyObservation(obs: NormalizedObservation, stackName: string): Norm
|
|
|
96
96
|
notes: obs.notes,
|
|
97
97
|
// Exports are already keyed by stack (#1279); nothing to qualify.
|
|
98
98
|
...(obs.stackExports ? { stackExports: obs.stackExports } : {}),
|
|
99
|
+
// Edge endpoints are resource ids, so they re-key with them (#4528).
|
|
100
|
+
...(obs.edges ? { edges: obs.edges.map((e) => ({ ...e, from: `${stackName}::${e.from}`, to: `${stackName}::${e.to}` })) } : {}),
|
|
99
101
|
};
|
|
100
102
|
}
|
|
101
103
|
|
|
@@ -518,13 +520,16 @@ function pushObservation(
|
|
|
518
520
|
// Dependencies ride alongside the managed resources so they become nodes, and
|
|
519
521
|
// carry `referencedBy` so every consumer can still tell the two apart.
|
|
520
522
|
const hasDependencies = Object.keys(dependencies.resources).length > 0;
|
|
523
|
+
// The read's own edges (#4528, an engine's graph) and the dependency
|
|
524
|
+
// closure's ride together; `buildLiveGraphIr` dedupes them.
|
|
525
|
+
const edges = [...(observed.edges ?? []), ...dependencies.edges];
|
|
521
526
|
observations.push({
|
|
522
527
|
lexicon,
|
|
523
528
|
resources: hasDependencies
|
|
524
529
|
? { ...observed.resources, ...dependencies.resources }
|
|
525
530
|
: observed.resources,
|
|
526
531
|
...(unobservedNames.length > 0 ? { unobserved: observed.unobserved } : {}),
|
|
527
|
-
...(
|
|
532
|
+
...(edges.length > 0 ? { edges } : {}),
|
|
528
533
|
...(observed.stackExports && Object.keys(observed.stackExports).length > 0
|
|
529
534
|
? { stackExports: observed.stackExports }
|
|
530
535
|
: {}),
|