@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
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Component } from "../../../../../../components/component";
|
|
2
|
+
import { phase } from "../../../../../../components/component";
|
|
3
|
+
|
|
4
|
+
/** COMP008 pass case: the component deploys only its own root, "dns". */
|
|
5
|
+
export const dns: Component = {
|
|
6
|
+
name: "dns",
|
|
7
|
+
archetype: "infra",
|
|
8
|
+
dependsOn: [],
|
|
9
|
+
deploy: [phase("Apply", [{ kind: "terraform-apply", root: "dns" }])],
|
|
10
|
+
};
|
|
@@ -54,7 +54,7 @@ describe("COMP000: component discovery errors", () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
describe("loadComponentChecks()", () => {
|
|
57
|
-
it("returns all
|
|
57
|
+
it("returns all eight COMP* checks, sorted by id, each error or warning severity", () => {
|
|
58
58
|
expect(checks.map((c) => c.id)).toEqual([
|
|
59
59
|
"COMP001",
|
|
60
60
|
"COMP002",
|
|
@@ -63,6 +63,7 @@ describe("loadComponentChecks()", () => {
|
|
|
63
63
|
"COMP005",
|
|
64
64
|
"COMP006",
|
|
65
65
|
"COMP007",
|
|
66
|
+
"COMP008",
|
|
66
67
|
]);
|
|
67
68
|
for (const check of checks) {
|
|
68
69
|
expect(["error", "warning"]).toContain(check.severity);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COMP008: applies-signal-only-root (#4490)
|
|
3
|
+
*
|
|
4
|
+
* Flags a component whose `terraform-apply` step deploys a root the project
|
|
5
|
+
* marks `signalOnly: true` in `terraform.roots` (#4484). Such a root belongs
|
|
6
|
+
* to another workspace member and is declared here only so a change to it
|
|
7
|
+
* brings this project into the change: the build skips it, so this
|
|
8
|
+
* project's graph does not draw it, and the live reads skip it too. A
|
|
9
|
+
* component that applies it would apply a root chant otherwise treats as
|
|
10
|
+
* the other member's, so the warning names the root and the member that
|
|
11
|
+
* owns it (the workspace member whose directory holds the root's `dir`).
|
|
12
|
+
*
|
|
13
|
+
* The roots come from the project's chant config, resolved by `chant lint`
|
|
14
|
+
* ({@link signalOnlyRootsOf}) and handed to the check on
|
|
15
|
+
* `ComponentCheckContext.signalOnlyRoots`. A check run without them (a
|
|
16
|
+
* direct unit test, a project with no terraform roots) flags nothing.
|
|
17
|
+
*
|
|
18
|
+
* Triggers on: `{ kind: "terraform-apply", root: "azure" }` where
|
|
19
|
+
* `terraform.roots.azure.signalOnly` is `true`.
|
|
20
|
+
* OK: the same step on a root without the marker, or no `terraform-apply`
|
|
21
|
+
* step naming the marked root.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
25
|
+
import type { ComponentCheck, ComponentCheckContext, ComponentCheckDiagnostic, SignalOnlyRoot } from "../../component-checks";
|
|
26
|
+
import { walkComponent } from "./support";
|
|
27
|
+
|
|
28
|
+
export const comp008AppliesSignalOnlyRootRule: ComponentCheck = {
|
|
29
|
+
id: "COMP008",
|
|
30
|
+
severity: "warning",
|
|
31
|
+
category: "correctness",
|
|
32
|
+
description: "A terraform-apply step deploys a root marked signalOnly, which another workspace member owns",
|
|
33
|
+
check(ctx: ComponentCheckContext): ComponentCheckDiagnostic[] {
|
|
34
|
+
const roots = ctx.signalOnlyRoots;
|
|
35
|
+
if (!roots || roots.size === 0) return [];
|
|
36
|
+
const diagnostics: ComponentCheckDiagnostic[] = [];
|
|
37
|
+
|
|
38
|
+
for (const [name, { component, filePath }] of ctx.components) {
|
|
39
|
+
const seen = new Set<string>();
|
|
40
|
+
for (const { step, phaseName } of walkComponent(component).steps) {
|
|
41
|
+
if (step.kind !== "terraform-apply") continue;
|
|
42
|
+
const root = (step as { root?: unknown }).root;
|
|
43
|
+
if (typeof root !== "string" || seen.has(root)) continue;
|
|
44
|
+
const marked = roots.get(root);
|
|
45
|
+
if (!marked) continue;
|
|
46
|
+
seen.add(root);
|
|
47
|
+
const owner = marked.owner
|
|
48
|
+
? `workspace member "${marked.owner}" owns it`
|
|
49
|
+
: "no workspace member was found to own it";
|
|
50
|
+
diagnostics.push({
|
|
51
|
+
checkId: "COMP008",
|
|
52
|
+
severity: "warning",
|
|
53
|
+
component: name,
|
|
54
|
+
file: filePath,
|
|
55
|
+
message:
|
|
56
|
+
`Component "${name}": the "terraform-apply" step (phase "${phaseName}") deploys root "${root}" ` +
|
|
57
|
+
`(${marked.dir}), which terraform.roots marks signalOnly; ${owner}. This project declares the root ` +
|
|
58
|
+
`only so a change to it brings the project into the change, and its build and live reads skip it. ` +
|
|
59
|
+
`Deploy the root from ${marked.owner ? `"${marked.owner}"` : "the member that owns it"}, or drop signalOnly ` +
|
|
60
|
+
`if this project owns it.`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return diagnostics;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The roots a project's `terraform.roots` marks `signalOnly`, keyed by root
|
|
71
|
+
* name, each with its `dir` as written and the workspace member whose
|
|
72
|
+
* directory holds it. `projectDir` is the directory holding the chant
|
|
73
|
+
* config. Tolerant: a config that does not load, or no marked root, gives an
|
|
74
|
+
* empty map; a workspace declaration that does not read leaves `owner` off.
|
|
75
|
+
*/
|
|
76
|
+
export async function signalOnlyRootsOf(projectDir: string): Promise<Map<string, SignalOnlyRoot>> {
|
|
77
|
+
const out = new Map<string, SignalOnlyRoot>();
|
|
78
|
+
let roots: Record<string, unknown> | undefined;
|
|
79
|
+
try {
|
|
80
|
+
const { loadChantConfig } = await import("../../../config");
|
|
81
|
+
const loaded = await loadChantConfig(projectDir);
|
|
82
|
+
roots = (loaded.config as { terraform?: { roots?: Record<string, unknown> } }).terraform?.roots;
|
|
83
|
+
} catch {
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
const marked: Array<[string, string]> = [];
|
|
87
|
+
for (const [name, entry] of Object.entries(roots ?? {})) {
|
|
88
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
89
|
+
const { dir, signalOnly } = entry as { dir?: unknown; signalOnly?: unknown };
|
|
90
|
+
if (signalOnly !== true || typeof dir !== "string") continue;
|
|
91
|
+
marked.push([name, dir]);
|
|
92
|
+
}
|
|
93
|
+
if (marked.length === 0) return out;
|
|
94
|
+
|
|
95
|
+
const ownerOfDir = await workspaceOwnerLookup(projectDir);
|
|
96
|
+
for (const [name, dir] of marked) {
|
|
97
|
+
const owner = ownerOfDir?.(resolve(projectDir, dir));
|
|
98
|
+
out.set(name, { dir, ...(owner ? { owner } : {}) });
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** A lookup from an absolute directory to the workspace member owning it, or undefined outside a readable workspace. */
|
|
104
|
+
async function workspaceOwnerLookup(projectDir: string): Promise<((abs: string) => string | undefined) | undefined> {
|
|
105
|
+
try {
|
|
106
|
+
const { findWorkspaceRoot } = await import("../../../project-root");
|
|
107
|
+
const found = findWorkspaceRoot(projectDir);
|
|
108
|
+
if (!found) return undefined;
|
|
109
|
+
const { readDeclaration, ownerOf } = await import("../../../workspace/declaration");
|
|
110
|
+
const { workingTree } = await import("../../../workspace/tree");
|
|
111
|
+
const declaration = readDeclaration(workingTree(found.dir));
|
|
112
|
+
return (abs: string) => {
|
|
113
|
+
const rel = relative(found.dir, abs);
|
|
114
|
+
if (rel.startsWith("..") || isAbsolute(rel)) return undefined;
|
|
115
|
+
const owner = ownerOf(declaration, [], rel === "" ? "." : rel.split("\\").join("/"));
|
|
116
|
+
return owner && "member" in owner ? owner.member.name : undefined;
|
|
117
|
+
};
|
|
118
|
+
} catch {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COMP008 (#4490): a component's terraform-apply step deploys a root the
|
|
3
|
+
* project marks signalOnly. The check runs over fixture components with the
|
|
4
|
+
* marked roots handed in, the way `chant lint` hands them; the config read
|
|
5
|
+
* (`signalOnlyRootsOf`) runs over a temporary workspace.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { afterAll, describe, expect, it } from "vitest";
|
|
12
|
+
import { runComponentChecks, type SignalOnlyRoot } from "../../component-checks";
|
|
13
|
+
import { comp008AppliesSignalOnlyRootRule, signalOnlyRootsOf } from "./comp008-applies-signal-only-root";
|
|
14
|
+
|
|
15
|
+
const FIXTURES = join(import.meta.dirname, "..", "__fixtures__", "comp", "comp008");
|
|
16
|
+
const MARKED = new Map<string, SignalOnlyRoot>([["azure", { dir: "../azure", owner: "azure" }]]);
|
|
17
|
+
|
|
18
|
+
describe("COMP008: applies-signal-only-root", () => {
|
|
19
|
+
it("warns on a terraform-apply step deploying a signalOnly root, naming the root and its owner", async () => {
|
|
20
|
+
const diagnostics = await runComponentChecks(join(FIXTURES, "fail"), [comp008AppliesSignalOnlyRootRule], { signalOnlyRoots: MARKED });
|
|
21
|
+
expect(diagnostics).toHaveLength(1);
|
|
22
|
+
const [d] = diagnostics;
|
|
23
|
+
expect(d.checkId).toBe("COMP008");
|
|
24
|
+
expect(d.severity).toBe("warning");
|
|
25
|
+
expect(d.component).toBe("dns");
|
|
26
|
+
expect(d.message).toContain('root "azure"');
|
|
27
|
+
expect(d.message).toContain('workspace member "azure" owns it');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("says no owner was found when the workspace names none", async () => {
|
|
31
|
+
const roots = new Map<string, SignalOnlyRoot>([["azure", { dir: "../azure" }]]);
|
|
32
|
+
const [d] = await runComponentChecks(join(FIXTURES, "fail"), [comp008AppliesSignalOnlyRootRule], { signalOnlyRoots: roots });
|
|
33
|
+
expect(d.message).toContain("no workspace member was found to own it");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("passes a component that deploys only its own roots", async () => {
|
|
37
|
+
const diagnostics = await runComponentChecks(join(FIXTURES, "pass"), [comp008AppliesSignalOnlyRootRule], { signalOnlyRoots: MARKED });
|
|
38
|
+
expect(diagnostics).toHaveLength(0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("flags nothing when no marked roots were resolved", async () => {
|
|
42
|
+
expect(await runComponentChecks(join(FIXTURES, "fail"), [comp008AppliesSignalOnlyRootRule])).toHaveLength(0);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("signalOnlyRootsOf", () => {
|
|
47
|
+
const ws = mkdtempSync(join(tmpdir(), "comp008-"));
|
|
48
|
+
afterAll(() => rmSync(ws, { recursive: true, force: true }));
|
|
49
|
+
// findWorkspaceRoot walks up to the git root, so the workspace root holds a .git.
|
|
50
|
+
mkdirSync(join(ws, ".git"));
|
|
51
|
+
mkdirSync(join(ws, "dns"));
|
|
52
|
+
mkdirSync(join(ws, "azure"));
|
|
53
|
+
writeFileSync(
|
|
54
|
+
join(ws, "chant.workspace.json"),
|
|
55
|
+
JSON.stringify({
|
|
56
|
+
name: "estate",
|
|
57
|
+
schema: 1,
|
|
58
|
+
members: [
|
|
59
|
+
{ name: "dns", dir: "dns", kind: "chant", because: "the dns zone" },
|
|
60
|
+
{ name: "azure", dir: "azure", kind: "chant", because: "the azure estate" },
|
|
61
|
+
],
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
writeFileSync(
|
|
65
|
+
join(ws, "dns", "chant.config.json"),
|
|
66
|
+
JSON.stringify({ terraform: { roots: { dns: { dir: "./estate" }, azure: { dir: "../azure", signalOnly: true } } } }),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
it("lists only the marked roots, with the member whose directory holds each", async () => {
|
|
70
|
+
const roots = await signalOnlyRootsOf(join(ws, "dns"));
|
|
71
|
+
expect([...roots]).toEqual([["azure", { dir: "../azure", owner: "azure" }]]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("gives an empty map for a project with no config", async () => {
|
|
75
|
+
expect((await signalOnlyRootsOf(join(ws, "azure"))).size).toBe(0);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -14,6 +14,7 @@ import { comp004GateNeedsDurableRuntimeRule } from "./comp004-gate-needs-durable
|
|
|
14
14
|
import { comp005CapabilityKindIsNounRule } from "./comp005-capability-kind-is-noun";
|
|
15
15
|
import { comp006ShellNeedsReasonRule } from "./comp006-shell-needs-reason";
|
|
16
16
|
import { comp007CompositionSprawlRule } from "./comp007-composition-sprawl";
|
|
17
|
+
import { comp008AppliesSignalOnlyRootRule } from "./comp008-applies-signal-only-root";
|
|
17
18
|
|
|
18
19
|
export { comp001PublishesNeverAppliesRule } from "./comp001-publishes-never-applies";
|
|
19
20
|
export { comp002DanglingWiringRefRule } from "./comp002-dangling-wiring-ref";
|
|
@@ -22,8 +23,9 @@ export { comp004GateNeedsDurableRuntimeRule } from "./comp004-gate-needs-durable
|
|
|
22
23
|
export { comp005CapabilityKindIsNounRule } from "./comp005-capability-kind-is-noun";
|
|
23
24
|
export { comp006ShellNeedsReasonRule } from "./comp006-shell-needs-reason";
|
|
24
25
|
export { comp007CompositionSprawlRule } from "./comp007-composition-sprawl";
|
|
26
|
+
export { comp008AppliesSignalOnlyRootRule, signalOnlyRootsOf } from "./comp008-applies-signal-only-root";
|
|
25
27
|
|
|
26
|
-
/** All
|
|
28
|
+
/** All eight COMP* checks, sorted by id. */
|
|
27
29
|
export function loadComponentChecks(): ComponentCheck[] {
|
|
28
30
|
return [
|
|
29
31
|
comp001PublishesNeverAppliesRule,
|
|
@@ -33,5 +35,6 @@ export function loadComponentChecks(): ComponentCheck[] {
|
|
|
33
35
|
comp005CapabilityKindIsNounRule,
|
|
34
36
|
comp006ShellNeedsReasonRule,
|
|
35
37
|
comp007CompositionSprawlRule,
|
|
38
|
+
comp008AppliesSignalOnlyRootRule,
|
|
36
39
|
];
|
|
37
40
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// An observation's own edges (chant#4528): the terraform lexicon's read of
|
|
2
|
+
// `choudoufu live-graph` returns block edges with its resources, and core
|
|
3
|
+
// carries them through the envelope to `LiveObservation.edges`.
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
import { mergeObservations, normalizeObservation, observation } from "./observation";
|
|
6
|
+
import { buildLiveGraphIr, type IREdge } from "./graph-ir";
|
|
7
|
+
|
|
8
|
+
const block: IREdge = { from: "web/module.app/aws_instance.web", to: "web/aws_s3_bucket.logs", kind: "ref", viaAttr: "dependencies", grain: "block" };
|
|
9
|
+
const instance: IREdge = { ...block, grain: "instance" };
|
|
10
|
+
|
|
11
|
+
describe("observation edges (#4528)", () => {
|
|
12
|
+
it("the envelope carries edges, and leaves the field off when there are none", () => {
|
|
13
|
+
expect(observation({}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, [block]).edges).toEqual([block]);
|
|
14
|
+
expect("edges" in observation({}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, [])).toBe(false);
|
|
15
|
+
expect(normalizeObservation(observation({}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, [block])).edges).toEqual([block]);
|
|
16
|
+
expect(normalizeObservation({}).edges).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("a merge keeps each edge once, and two grains between one pair as two", () => {
|
|
20
|
+
const part = normalizeObservation(observation({}, undefined, undefined, undefined, undefined, undefined, undefined, undefined, [block, instance]));
|
|
21
|
+
expect(mergeObservations([part, part]).edges).toEqual([block, instance]);
|
|
22
|
+
expect(mergeObservations([normalizeObservation({})]).edges).toBeUndefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("buildLiveGraphIr keeps an observation edge whose endpoints were both observed", () => {
|
|
26
|
+
const resources = {
|
|
27
|
+
"web/module.app/aws_instance.web": { type: "Terraform::Resource", status: "bound" },
|
|
28
|
+
"web/aws_s3_bucket.logs": { type: "Terraform::Resource", status: "bound" },
|
|
29
|
+
};
|
|
30
|
+
const ir = buildLiveGraphIr([{ lexicon: "terraform", resources, edges: [block, { ...block, to: "web/aws_sqs_queue.gone" }] }]);
|
|
31
|
+
expect(ir.edges).toEqual([block]);
|
|
32
|
+
});
|
|
33
|
+
});
|
package/src/observation.ts
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
import type { ResourceMetadata } from "./lexicon";
|
|
36
|
+
import type { IREdge } from "./graph-ir";
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* Why a declared entity was not observed. Total: a lexicon that cannot observe
|
|
@@ -49,13 +50,18 @@ import type { ResourceMetadata } from "./lexicon";
|
|
|
49
50
|
* filter (`owned: true` against a resource carrying no chant marker). The
|
|
50
51
|
* result deliberately says nothing about it, which is still not absence: a
|
|
51
52
|
* declared resource that exists but is foreign must not classify as `create`.
|
|
53
|
+
* - `not-reached` — the read that would have answered never reported this
|
|
54
|
+
* entity (#4451): a drift stream settled without a row for an address its
|
|
55
|
+
* start line expected, or a read was never run for it at all. Says nothing
|
|
56
|
+
* about the entity, only that no answer arrived.
|
|
52
57
|
*/
|
|
53
58
|
export type UnobservedReason =
|
|
54
59
|
| "read-failed"
|
|
55
60
|
| "no-credentials"
|
|
56
61
|
| "no-binding"
|
|
57
62
|
| "unsupported-kind"
|
|
58
|
-
| "filtered"
|
|
63
|
+
| "filtered"
|
|
64
|
+
| "not-reached";
|
|
59
65
|
|
|
60
66
|
/**
|
|
61
67
|
* Every legal {@link UnobservedReason}, for validation and conformance checks.
|
|
@@ -79,6 +85,7 @@ const UNOBSERVED_REASON_WITNESS: Record<UnobservedReason, true> = {
|
|
|
79
85
|
"no-binding": true,
|
|
80
86
|
"unsupported-kind": true,
|
|
81
87
|
filtered: true,
|
|
88
|
+
"not-reached": true,
|
|
82
89
|
};
|
|
83
90
|
|
|
84
91
|
export const UNOBSERVED_REASONS: readonly UnobservedReason[] = Object.keys(
|
|
@@ -192,6 +199,16 @@ export interface ObservationResult {
|
|
|
192
199
|
* question. Values are already scrubbed of anything that looks secret.
|
|
193
200
|
*/
|
|
194
201
|
stackExports?: Record<string, Record<string, unknown>>;
|
|
202
|
+
/**
|
|
203
|
+
* Relationships the read observed between the resources it returned
|
|
204
|
+
* (chant#4528): an ownership engine's graph, such as the block edges
|
|
205
|
+
* `choudoufu live-graph` prints, with their `grain`. Core carries them to
|
|
206
|
+
* `LiveObservation.edges` beside the ones `describeDependencies` reports;
|
|
207
|
+
* `buildLiveGraphIr` drops any whose endpoints were not both observed.
|
|
208
|
+
* `from`/`to` are node ids in the space of {@link resources} keys and the
|
|
209
|
+
* instance node ids built from them.
|
|
210
|
+
*/
|
|
211
|
+
edges?: IREdge[];
|
|
195
212
|
}
|
|
196
213
|
|
|
197
214
|
/**
|
|
@@ -230,6 +247,8 @@ export interface NormalizedObservation {
|
|
|
230
247
|
notes: string[];
|
|
231
248
|
/** Per-stack exports (#1279), keyed by stack name. Absent when the lexicon reported none. */
|
|
232
249
|
stackExports?: Record<string, Record<string, unknown>>;
|
|
250
|
+
/** Observed relationships (chant#4528). Absent when the lexicon reported none. */
|
|
251
|
+
edges?: IREdge[];
|
|
233
252
|
}
|
|
234
253
|
|
|
235
254
|
/** True when `value` is the versioned {@link ObservationResult} envelope. */
|
|
@@ -254,6 +273,7 @@ export function observation(
|
|
|
254
273
|
sources?: Record<string, string>,
|
|
255
274
|
annotations?: Record<string, Record<string, unknown>>,
|
|
256
275
|
rootReads?: Record<string, RootRead>,
|
|
276
|
+
edges?: IREdge[],
|
|
257
277
|
): ObservationResult {
|
|
258
278
|
return {
|
|
259
279
|
observation: "v1",
|
|
@@ -265,6 +285,7 @@ export function observation(
|
|
|
265
285
|
...(rootReads && Object.keys(rootReads).length > 0 ? { rootReads } : {}),
|
|
266
286
|
...(notes && notes.length > 0 ? { notes } : {}),
|
|
267
287
|
...(stackExports && Object.keys(stackExports).length > 0 ? { stackExports } : {}),
|
|
288
|
+
...(edges && edges.length > 0 ? { edges } : {}),
|
|
268
289
|
};
|
|
269
290
|
}
|
|
270
291
|
|
|
@@ -286,6 +307,7 @@ export function normalizeObservation(value: DescribeResourcesResult | undefined)
|
|
|
286
307
|
...(value.annotations && Object.keys(value.annotations).length > 0 ? { annotations: value.annotations } : {}),
|
|
287
308
|
...(value.rootReads && Object.keys(value.rootReads).length > 0 ? { rootReads: value.rootReads } : {}),
|
|
288
309
|
...(value.stackExports && Object.keys(value.stackExports).length > 0 ? { stackExports: value.stackExports } : {}),
|
|
310
|
+
...(Array.isArray(value.edges) && value.edges.length > 0 ? { edges: value.edges } : {}),
|
|
289
311
|
};
|
|
290
312
|
}
|
|
291
313
|
return { resources: value, unobserved: {}, queried: {}, sources: {}, notes: [] };
|
|
@@ -339,6 +361,10 @@ export function mergeObservations(parts: Iterable<NormalizedObservation>): Norma
|
|
|
339
361
|
// Annotations (#4372) merge like `sources`: per entity, disjoint parts.
|
|
340
362
|
const annotations: Record<string, Record<string, unknown>> = {};
|
|
341
363
|
const rootReads: Record<string, RootRead> = {};
|
|
364
|
+
// Edges (#4528) concatenate, once each: the parts read disjoint entity sets,
|
|
365
|
+
// and two parts reporting the same edge is one edge.
|
|
366
|
+
const edges: IREdge[] = [];
|
|
367
|
+
const edgeKeys = new Set<string>();
|
|
342
368
|
for (const part of parts) {
|
|
343
369
|
Object.assign(resources, part.resources);
|
|
344
370
|
Object.assign(unobserved, part.unobserved);
|
|
@@ -348,6 +374,12 @@ export function mergeObservations(parts: Iterable<NormalizedObservation>): Norma
|
|
|
348
374
|
Object.assign(rootReads, part.rootReads ?? {});
|
|
349
375
|
for (const n of part.notes) notes.add(n);
|
|
350
376
|
Object.assign(stackExports, part.stackExports ?? {});
|
|
377
|
+
for (const e of part.edges ?? []) {
|
|
378
|
+
const key = `${e.from}\0${e.to}\0${e.viaAttr ?? ""}\0${e.toAttr ?? ""}\0${e.grain ?? ""}`;
|
|
379
|
+
if (edgeKeys.has(key)) continue;
|
|
380
|
+
edgeKeys.add(key);
|
|
381
|
+
edges.push(e);
|
|
382
|
+
}
|
|
351
383
|
}
|
|
352
384
|
// Present wins: a stack that could not be read does not un-observe a resource
|
|
353
385
|
// another stack returned.
|
|
@@ -361,6 +393,7 @@ export function mergeObservations(parts: Iterable<NormalizedObservation>): Norma
|
|
|
361
393
|
...(Object.keys(annotations).length > 0 ? { annotations } : {}),
|
|
362
394
|
...(Object.keys(rootReads).length > 0 ? { rootReads } : {}),
|
|
363
395
|
...(Object.keys(stackExports).length > 0 ? { stackExports } : {}),
|
|
396
|
+
...(edges.length > 0 ? { edges } : {}),
|
|
364
397
|
};
|
|
365
398
|
}
|
|
366
399
|
|
|
@@ -377,6 +410,8 @@ export function unobservedReasonText(reason: UnobservedReason): string {
|
|
|
377
410
|
return "no reader for this resource kind";
|
|
378
411
|
case "filtered":
|
|
379
412
|
return "withheld by the --owned filter";
|
|
413
|
+
case "not-reached":
|
|
414
|
+
return "the read never reported it";
|
|
380
415
|
}
|
|
381
416
|
}
|
|
382
417
|
|
|
@@ -309,7 +309,7 @@ export const lifecyclePlanChangeSetContract = activityContract(
|
|
|
309
309
|
|
|
310
310
|
export const readChangeSetPartContract = activityContract(
|
|
311
311
|
"readChangeSetPart",
|
|
312
|
-
z.strictObject({ member: z.string(), planner: z.enum(["chant", "warden", "sql"]), file: z.string(), lexicon: z.string().optional() }),
|
|
312
|
+
z.strictObject({ member: z.string(), planner: z.enum(["chant", "warden", "sql", "pulumi"]), file: z.string(), lexicon: z.string().optional() }),
|
|
313
313
|
z.object({ part: changeSetPart }),
|
|
314
314
|
);
|
|
315
315
|
|
|
@@ -90,10 +90,11 @@ export interface ReadChangeSetPartArgs {
|
|
|
90
90
|
member: string;
|
|
91
91
|
/**
|
|
92
92
|
* Which planner wrote the file: `chant` (`lifecycle plan --json`), `warden`
|
|
93
|
-
* (its reconcile change sets),
|
|
94
|
-
* `change-set` adapters produced, written as JSON)
|
|
93
|
+
* (its reconcile change sets), `sql` (a part the sql lexicon's
|
|
94
|
+
* `change-set` adapters produced, written as JSON), or `pulumi` (a part
|
|
95
|
+
* the pulumi lexicon's `pulumiPreviewPart` produced, written as JSON).
|
|
95
96
|
*/
|
|
96
|
-
planner: "chant" | "warden" | "sql";
|
|
97
|
+
planner: "chant" | "warden" | "sql" | "pulumi";
|
|
97
98
|
/** The plan file, relative to the current directory. */
|
|
98
99
|
file: string;
|
|
99
100
|
/** The lexicon the entries belong to, when the plan does not say. */
|
|
@@ -104,7 +105,8 @@ export interface ReadChangeSetPartArgs {
|
|
|
104
105
|
* A plan another step or job wrote to a file, as a member's part. A warden
|
|
105
106
|
* plan is the reconcile `ChangeSet` (`@intentius/chant/reconcile`), or an
|
|
106
107
|
* array of them, one per cycle and org. A sql plan is already a part
|
|
107
|
-
* (`@intentius/chant-lexicon-sql/change-set`), renamed to `member` here
|
|
108
|
+
* (`@intentius/chant-lexicon-sql/change-set`), renamed to `member` here,
|
|
109
|
+
* and so is a pulumi one (`@intentius/chant-lexicon-pulumi/change-set`).
|
|
108
110
|
*/
|
|
109
111
|
export async function readChangeSetPart(args: ReadChangeSetPartArgs): Promise<ChangeSetPartResult> {
|
|
110
112
|
const plan: unknown = JSON.parse(readFileSync(resolve(args.file), "utf-8"));
|
|
@@ -115,7 +117,8 @@ export async function readChangeSetPart(args: ReadChangeSetPartArgs): Promise<Ch
|
|
|
115
117
|
case "warden":
|
|
116
118
|
return { part: reconcilePlanPart({ member: args.member, plan: plan as ReconcileChangeSet | ReconcileChangeSet[], ...lexicon }) };
|
|
117
119
|
case "sql":
|
|
118
|
-
|
|
120
|
+
case "pulumi":
|
|
121
|
+
return { part: readChangeSetPartFile(plan, args.planner, args.member, args.lexicon) };
|
|
119
122
|
default:
|
|
120
123
|
throw new Error(`readChangeSetPart: no adapter for planner ${String(args.planner)}; terraform-family plans come from terraformPlan's changeSet`);
|
|
121
124
|
}
|
|
@@ -32,3 +32,39 @@ export class NonRetryableActivityError extends Error {
|
|
|
32
32
|
export function nonRetryableFailure(message: string, type: string): NonRetryableActivityError {
|
|
33
33
|
return new NonRetryableActivityError(message, type);
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The property a failing activity sets on the error it throws to publish
|
|
38
|
+
* outputs it already knew when it failed (chant#4512). terraformInit knows its
|
|
39
|
+
* root's directory once the root resolves and before `terraform init` runs, so
|
|
40
|
+
* a failed init still names the root it was for.
|
|
41
|
+
*
|
|
42
|
+
* It is a plain property, not a class, so a lexicon can set it without
|
|
43
|
+
* importing anything newer from core: an executor that predates it reads the
|
|
44
|
+
* error as any other failure.
|
|
45
|
+
*/
|
|
46
|
+
export const KNOWN_OUTPUTS_KEY = "knownOutputs";
|
|
47
|
+
|
|
48
|
+
/** An activity error carrying the outputs it knew before it failed (chant#4512). */
|
|
49
|
+
export type ErrorWithKnownOutputs = Error & { [KNOWN_OUTPUTS_KEY]: Record<string, unknown> };
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Attach `outputs` to `err` as the outputs the activity knew when it failed,
|
|
53
|
+
* and hand the error back for the activity to rethrow. A thrown non-Error is
|
|
54
|
+
* wrapped in one, so its message survives.
|
|
55
|
+
*/
|
|
56
|
+
export function withKnownOutputs(err: unknown, outputs: Record<string, unknown>): ErrorWithKnownOutputs {
|
|
57
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
58
|
+
return Object.assign(e, { [KNOWN_OUTPUTS_KEY]: outputs });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The outputs a failed activity published on its error (chant#4512), or
|
|
63
|
+
* undefined when it published none. An executor resolves a failed step's
|
|
64
|
+
* `outcomeAttribute` paths against these, as it does against a result.
|
|
65
|
+
*/
|
|
66
|
+
export function knownOutputsOf(err: unknown): Record<string, unknown> | undefined {
|
|
67
|
+
if (typeof err !== "object" || err === null) return undefined;
|
|
68
|
+
const known = (err as Record<string, unknown>)[KNOWN_OUTPUTS_KEY];
|
|
69
|
+
return typeof known === "object" && known !== null && !Array.isArray(known) ? (known as Record<string, unknown>) : undefined;
|
|
70
|
+
}
|
package/src/op/index.ts
CHANGED
|
@@ -123,7 +123,8 @@ export { loadActivityContracts, mergeActivityContracts } from "./activity-contra
|
|
|
123
123
|
export type { LexiconActivityContractContributor } from "./activity-contract-registry";
|
|
124
124
|
export { ACTIVITY_PROFILES, ACTIVITY_PROFILE_NAMES, MAX_STEP_TIMEOUT } from "./activity-profiles";
|
|
125
125
|
export type { ActivityProfile, ActivityProfileName } from "./activity-profiles";
|
|
126
|
-
export { NonRetryableActivityError, nonRetryableFailure } from "./activity-failure";
|
|
126
|
+
export { NonRetryableActivityError, nonRetryableFailure, KNOWN_OUTPUTS_KEY, withKnownOutputs, knownOutputsOf } from "./activity-failure";
|
|
127
|
+
export type { ErrorWithKnownOutputs } from "./activity-failure";
|
|
127
128
|
export { runOpLocally, parseDuration, OpRunFailure } from "./local-executor";
|
|
128
129
|
export type { StepRecord, OpRunResult, RunOpOptions } from "./local-executor";
|
|
129
130
|
export { currentOpRun, withOpRunContext } from "./run-context";
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import { memoryGateLedgerPort, type GateLedgerPort } from "./gate";
|
|
10
10
|
import { computePlanDigest } from "../lifecycle/plan-digest";
|
|
11
11
|
import { renderHuman } from "./local-output";
|
|
12
|
+
import { knownOutputsOf, withKnownOutputs } from "./activity-failure";
|
|
12
13
|
|
|
13
14
|
// #3521: whether the workspace seals a gate is read from identity.gates in the
|
|
14
15
|
// working directory; a test says which gates are sealed instead.
|
|
@@ -264,6 +265,85 @@ describe("runOpLocally — outcomeAttribute", () => {
|
|
|
264
265
|
});
|
|
265
266
|
});
|
|
266
267
|
|
|
268
|
+
describe("runOpLocally — a failed step records the outputs it knew (#4512)", () => {
|
|
269
|
+
/** The failed run's records, off the OpRunFailure it rejects with. */
|
|
270
|
+
async function failedRecords(config: OpConfig, activities: Map<string, ActivityFn>, profiles = PROFILES) {
|
|
271
|
+
const failure = await runOpLocally(config, activities, profiles).then(
|
|
272
|
+
() => undefined,
|
|
273
|
+
(err: unknown) => err,
|
|
274
|
+
);
|
|
275
|
+
expect(failure).toBeInstanceOf(OpRunFailure);
|
|
276
|
+
return (failure as OpRunFailure).result.records;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const initConfig = op({
|
|
280
|
+
phases: [
|
|
281
|
+
{ name: "Init", steps: [{ kind: "activity", fn: "init", outcomeAttribute: [{ name: "TerraformRootPath", from: "rootPath" }, { name: "Workspace", from: "workspace" }] }] },
|
|
282
|
+
{ name: "Plan", steps: [{ kind: "activity", fn: "plan" }] },
|
|
283
|
+
],
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("a failed step's outcomes come from the known outputs on its error", async () => {
|
|
287
|
+
const init: ActivityFn = async () => {
|
|
288
|
+
throw withKnownOutputs(new Error("terraform init: backend unreachable"), { dir: "/w/estates/aws", rootPath: "estates/aws" });
|
|
289
|
+
};
|
|
290
|
+
let planRan = false;
|
|
291
|
+
const plan: ActivityFn = async () => {
|
|
292
|
+
planRan = true;
|
|
293
|
+
return {};
|
|
294
|
+
};
|
|
295
|
+
const records = await failedRecords(initConfig, new Map<string, ActivityFn>([["init", init], ["plan", plan]]));
|
|
296
|
+
expect(records[0]).toMatchObject({ phase: "Init", fn: "init", status: "fail", error: expect.stringContaining("backend unreachable") });
|
|
297
|
+
// Workspace is not among the known outputs, so it is left off.
|
|
298
|
+
expect(records[0].outcomes).toEqual([{ name: "TerraformRootPath", value: "estates/aws" }]);
|
|
299
|
+
expect(records[0].outcome).toEqual({ name: "TerraformRootPath", value: "estates/aws" });
|
|
300
|
+
// A failed phase ends the run there: Plan does not run, and a failure (unlike
|
|
301
|
+
// a gate, a decision point or a stopped claim) leaves no record for it.
|
|
302
|
+
expect(planRan).toBe(false);
|
|
303
|
+
expect(records.filter((r) => r.phase === "Plan")).toEqual([]);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("a failed step with no known outputs records no outcome, as before", async () => {
|
|
307
|
+
const init: ActivityFn = async () => {
|
|
308
|
+
throw new Error("chant.config.ts failed to evaluate");
|
|
309
|
+
};
|
|
310
|
+
const records = await failedRecords(initConfig, new Map<string, ActivityFn>([["init", init], ["plan", async () => ({})]]));
|
|
311
|
+
expect(records[0].status).toBe("fail");
|
|
312
|
+
expect(records[0].outcome).toBeUndefined();
|
|
313
|
+
expect(records[0].outcomes).toBeUndefined();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("a property set by hand, without core's helper, is read the same way", async () => {
|
|
317
|
+
const init: ActivityFn = async () => {
|
|
318
|
+
throw Object.assign(new Error("init failed"), { knownOutputs: { rootPath: "roots/app", workspace: "prod" } });
|
|
319
|
+
};
|
|
320
|
+
const records = await failedRecords(initConfig, new Map<string, ActivityFn>([["init", init], ["plan", async () => ({})]]));
|
|
321
|
+
expect(records[0].outcomes).toEqual([{ name: "TerraformRootPath", value: "roots/app" }, { name: "Workspace", value: "prod" }]);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("after retries, the last attempt's known outputs are the ones recorded", async () => {
|
|
325
|
+
let calls = 0;
|
|
326
|
+
const init: ActivityFn = async () => {
|
|
327
|
+
calls++;
|
|
328
|
+
throw withKnownOutputs(new Error(`attempt ${calls}`), { rootPath: `attempt-${calls}` });
|
|
329
|
+
};
|
|
330
|
+
const config = op({
|
|
331
|
+
phases: [{ name: "Init", steps: [{ kind: "activity", fn: "init", outcomeAttribute: { name: "TerraformRootPath", from: "rootPath" } }] }],
|
|
332
|
+
});
|
|
333
|
+
const records = await failedRecords(config, new Map<string, ActivityFn>([["init", init]]));
|
|
334
|
+
expect(calls).toBe(3);
|
|
335
|
+
expect(records[0].outcomes).toEqual([{ name: "TerraformRootPath", value: "attempt-3" }]);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("a thrown non-Error keeps its message when known outputs are attached", async () => {
|
|
339
|
+
expect(withKnownOutputs("plain string", { a: 1 }).message).toBe("plain string");
|
|
340
|
+
expect(knownOutputsOf(withKnownOutputs("plain string", { a: 1 }))).toEqual({ a: 1 });
|
|
341
|
+
expect(knownOutputsOf(new Error("none"))).toBeUndefined();
|
|
342
|
+
expect(knownOutputsOf(Object.assign(new Error("list"), { knownOutputs: ["x"] }))).toBeUndefined();
|
|
343
|
+
expect(knownOutputsOf(undefined)).toBeUndefined();
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
267
347
|
describe("runOpLocally — onFailure", () => {
|
|
268
348
|
test("runs compensation phases in reverse and rejects with status=fail", async () => {
|
|
269
349
|
const order: string[] = [];
|