@intentius/chant 0.56.0 → 0.58.0
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/catalog.d.ts +51 -1
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +20 -1
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +14 -8
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/lineage.d.ts +10 -0
- package/dist/audit/lineage.d.ts.map +1 -0
- package/dist/audit/prior-art.d.ts +385 -0
- package/dist/audit/prior-art.d.ts.map +1 -0
- package/dist/audit/proof.d.ts +16 -0
- package/dist/audit/proof.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +8 -1
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts +3 -0
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +15 -0
- package/dist/audit/secrets.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +46 -0
- package/dist/audit/terraform-state.d.ts.map +1 -0
- package/dist/build.d.ts +17 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/claimed-fields.d.ts +161 -0
- package/dist/claimed-fields.d.ts.map +1 -0
- package/dist/cli/build-params-cli.d.ts +2 -2
- package/dist/cli/command-group.d.ts +14 -0
- package/dist/cli/command-group.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +17 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
- package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +2 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +82 -16
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +49 -42
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts +28 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts +16 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/resources/context.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +26 -9
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
- package/dist/components/auto-release.d.ts +10 -20
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability.d.ts +4 -4
- package/dist/components/cli-support.d.ts +20 -21
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +3 -3
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver-output.d.ts.map +1 -1
- package/dist/components/driver.d.ts +66 -36
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/manifest-persistence.d.ts +1 -9
- package/dist/components/manifest-persistence.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +6 -6
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +9 -15
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts +2 -2
- package/dist/deep-observation.d.ts +17 -0
- package/dist/deep-observation.d.ts.map +1 -1
- package/dist/discovery/files.d.ts +4 -4
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +33 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
- package/dist/discovery/sandbox/config-wire.d.ts +2 -2
- package/dist/fold/foldable-helpers.d.ts +5 -1
- package/dist/fold/foldable-helpers.d.ts.map +1 -1
- package/dist/fold-provenance.d.ts +236 -0
- package/dist/fold-provenance.d.ts.map +1 -0
- package/dist/graph-ops.d.ts +1 -1
- package/dist/held-elsewhere.d.ts +85 -0
- package/dist/held-elsewhere.d.ts.map +1 -0
- package/dist/import/ir-utils.d.ts +1 -1
- package/dist/import/ir-utils.d.ts.map +1 -1
- package/dist/import/parser.d.ts +33 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts +3 -3
- package/dist/lexicon-config.d.ts +4 -4
- package/dist/lexicon-plugin-helpers.d.ts +1 -1
- package/dist/lexicon.d.ts +104 -14
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +13 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/deep-diff.d.ts +133 -6
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +71 -3
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +2 -2
- package/dist/lifecycle/run-ledger.d.ts +53 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +3 -3
- package/dist/lifecycle/symptoms.d.ts.map +1 -1
- package/dist/lint/config.d.ts +81 -2
- package/dist/lint/config.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +55 -2
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
- package/dist/lint/rules/comp/index.d.ts +1 -1
- package/dist/lint/rules/comp/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.d.ts +16 -0
- package/dist/lint/rules/op/index.d.ts.map +1 -0
- package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
- package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
- package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
- package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
- package/dist/lint/rules/op/support.d.ts +12 -0
- package/dist/lint/rules/op/support.d.ts.map +1 -0
- package/dist/lint/suppressions.d.ts +108 -0
- package/dist/lint/suppressions.d.ts.map +1 -0
- package/dist/live-endpoint.d.ts +1 -1
- package/dist/op/activities/activity-contracts.d.ts +54 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -0
- package/dist/op/activities/apply.d.ts +262 -0
- package/dist/op/activities/apply.d.ts.map +1 -0
- package/dist/op/activities/build.d.ts +13 -0
- package/dist/op/activities/build.d.ts.map +1 -0
- package/dist/op/activities/converge.d.ts +110 -0
- package/dist/op/activities/converge.d.ts.map +1 -0
- package/dist/op/activities/env-teardown.d.ts +76 -0
- package/dist/op/activities/env-teardown.d.ts.map +1 -0
- package/dist/op/activities/guard-validate.d.ts +64 -0
- package/dist/op/activities/guard-validate.d.ts.map +1 -0
- package/dist/op/activities/http-check.d.ts +34 -0
- package/dist/op/activities/http-check.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +31 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
- package/dist/op/activities/lifecycle.d.ts +42 -0
- package/dist/op/activities/lifecycle.d.ts.map +1 -0
- package/dist/op/activities/pipeline-audit.d.ts +84 -0
- package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
- package/dist/op/activities/policy.d.ts +17 -0
- package/dist/op/activities/policy.d.ts.map +1 -0
- package/dist/op/activities/reconcile.d.ts +89 -0
- package/dist/op/activities/reconcile.d.ts.map +1 -0
- package/dist/op/activities/shell.d.ts +13 -0
- package/dist/op/activities/shell.d.ts.map +1 -0
- package/dist/op/activities/teardown.d.ts +10 -0
- package/dist/op/activities/teardown.d.ts.map +1 -0
- package/dist/op/activities/util.d.ts +7 -0
- package/dist/op/activities/util.d.ts.map +1 -0
- package/dist/op/activities/wait.d.ts +28 -0
- package/dist/op/activities/wait.d.ts.map +1 -0
- package/dist/op/activities/workflow-audit.d.ts +92 -0
- package/dist/op/activities/workflow-audit.d.ts.map +1 -0
- package/dist/op/activity-contract-registry.d.ts +76 -0
- package/dist/op/activity-contract-registry.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +25 -18
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-failure.d.ts +28 -0
- package/dist/op/activity-failure.d.ts.map +1 -0
- package/dist/op/activity-profiles.d.ts +118 -0
- package/dist/op/activity-profiles.d.ts.map +1 -0
- package/dist/op/activity-registry.d.ts +22 -38
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/activity-runtime.d.ts +3 -14
- package/dist/op/activity-runtime.d.ts.map +1 -1
- package/dist/op/builders.d.ts +84 -29
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/apply-op.d.ts +113 -0
- package/dist/op/composites/apply-op.d.ts.map +1 -0
- package/dist/op/composites/converge-op.d.ts +120 -0
- package/dist/op/composites/converge-op.d.ts.map +1 -0
- package/dist/op/composites/index.d.ts +26 -0
- package/dist/op/composites/index.d.ts.map +1 -0
- package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
- package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
- package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
- package/dist/op/composites/reconcile-op.d.ts +57 -0
- package/dist/op/composites/reconcile-op.d.ts.map +1 -0
- package/dist/op/composites/watch-op.d.ts +60 -0
- package/dist/op/composites/watch-op.d.ts.map +1 -0
- package/dist/op/composites/workflow-audit-op.d.ts +57 -0
- package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
- package/dist/op/converge-rule.d.ts +12 -10
- package/dist/op/converge-rule.d.ts.map +1 -1
- package/dist/op/cron.d.ts +51 -0
- package/dist/op/cron.d.ts.map +1 -0
- package/dist/op/discover.d.ts +9 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/duration.d.ts +11 -0
- package/dist/op/duration.d.ts.map +1 -0
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/gate.d.ts +90 -0
- package/dist/op/gate.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +9 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -1
- package/dist/op/index.d.ts +23 -7
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +114 -28
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts +11 -1
- package/dist/op/local-output.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +184 -0
- package/dist/op/op-ir.d.ts.map +1 -0
- package/dist/op/op-verb-class.d.ts +10 -2
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts +30 -5
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/receipt-store.d.ts +1 -1
- package/dist/op/receipt-store.d.ts.map +1 -1
- package/dist/op/resource.d.ts +15 -1
- package/dist/op/resource.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +200 -0
- package/dist/op/runtime.d.ts.map +1 -0
- package/dist/op/runtimes/local.d.ts +29 -0
- package/dist/op/runtimes/local.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +15 -13
- package/dist/op/step-output-ref.d.ts.map +1 -1
- package/dist/op/types.d.ts +79 -27
- package/dist/op/types.d.ts.map +1 -1
- package/dist/ownership.d.ts +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/serializer-walker.d.ts.map +1 -1
- package/dist/stack-output.d.ts +5 -0
- package/dist/stack-output.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/audit/catalog.test.ts +79 -3
- package/src/audit/catalog.ts +111 -13
- package/src/audit/core.test.ts +26 -0
- package/src/audit/core.ts +61 -17
- package/src/audit/discover.test.ts +39 -0
- package/src/audit/discover.ts +91 -15
- package/src/audit/lineage.ts +100 -0
- package/src/audit/prior-art.ts +153 -0
- package/src/audit/proof.test.ts +67 -0
- package/src/audit/proof.ts +70 -0
- package/src/audit/report-html.ts +9 -4
- package/src/audit/report-model.ts +16 -2
- package/src/audit/report.test.ts +33 -0
- package/src/audit/report.ts +5 -3
- package/src/audit/rules-doc.test.ts +34 -2
- package/src/audit/rules-doc.ts +82 -9
- package/src/audit/secrets.ts +15 -3
- package/src/audit/terraform-state.test.ts +144 -0
- package/src/audit/terraform-state.ts +131 -0
- package/src/build.ts +31 -1
- package/src/claimed-fields.test.ts +145 -0
- package/src/claimed-fields.ts +208 -0
- package/src/cli/build-params-cli.ts +2 -2
- package/src/cli/command-group.ts +14 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
- package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
- package/src/cli/commands/audit.test.ts +189 -11
- package/src/cli/commands/audit.ts +73 -27
- package/src/cli/commands/build.test.ts +292 -9
- package/src/cli/commands/build.ts +156 -24
- package/src/cli/commands/check-lexicon-docs.ts +1 -1
- package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
- package/src/cli/commands/check-lexicon-examples.ts +42 -10
- package/src/cli/commands/import.ts +6 -0
- package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
- package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
- package/src/cli/commands/init-lexicon.test.ts +97 -2
- package/src/cli/commands/init-lexicon.ts +4 -2
- package/src/cli/commands/init.test.ts +35 -1
- package/src/cli/commands/init.ts +14 -3
- package/src/cli/commands/lint.test.ts +48 -0
- package/src/cli/commands/lint.ts +123 -0
- package/src/cli/commands/onboard.test.ts +75 -0
- package/src/cli/commands/onboard.ts +54 -23
- package/src/cli/handlers/build.test.ts +81 -1
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/graph.test.ts +3 -3
- package/src/cli/handlers/lifecycle.test.ts +195 -12
- package/src/cli/handlers/lifecycle.ts +123 -6
- package/src/cli/handlers/misc.ts +1 -1
- package/src/cli/handlers/operator.test.ts +183 -11
- package/src/cli/handlers/operator.ts +260 -55
- package/src/cli/handlers/run.test.ts +431 -1287
- package/src/cli/handlers/run.ts +365 -915
- package/src/cli/main.test.ts +23 -15
- package/src/cli/main.ts +88 -33
- package/src/cli/mcp/op-tools.ts +147 -88
- package/src/cli/mcp/resource-handlers.ts +10 -37
- package/src/cli/mcp/resources/context.ts +11 -12
- package/src/cli/mcp/server.test.ts +25 -27
- package/src/cli/mcp/server.ts +2 -2
- package/src/cli/registry.ts +26 -9
- package/src/codegen/docs.ts +1 -1
- package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
- package/src/codegen/generate-post-synth-barrel.ts +1 -1
- package/src/components/README.md +3 -2
- package/src/components/auto-release.test.ts +4 -20
- package/src/components/auto-release.ts +17 -42
- package/src/components/capability.ts +4 -4
- package/src/components/cli-support.test.ts +10 -5
- package/src/components/cli-support.ts +52 -44
- package/src/components/component.schema.json +2 -2
- package/src/components/component.ts +3 -3
- package/src/components/driver-output.ts +14 -2
- package/src/components/driver.test.ts +92 -22
- package/src/components/driver.ts +193 -83
- package/src/components/index.ts +1 -6
- package/src/components/manifest-persistence.test.ts +2 -20
- package/src/components/manifest-persistence.ts +4 -24
- package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
- package/src/components/run-progress.ts +6 -6
- package/src/components/verbs/run-agent.ts +11 -17
- package/src/components/verbs/verify.ts +2 -2
- package/src/deep-observation.test.ts +75 -0
- package/src/deep-observation.ts +48 -0
- package/src/discovery/collect.test.ts +3 -3
- package/src/discovery/files.ts +4 -4
- package/src/discovery/fold-import.ts +105 -2
- package/src/discovery/param-deps.test.ts +112 -1
- package/src/discovery/param-deps.ts +193 -0
- package/src/discovery/sandbox/bundle.ts +15 -1
- package/src/discovery/sandbox/config-wire.test.ts +2 -2
- package/src/discovery/sandbox/config-wire.ts +2 -2
- package/src/fold/fold.test.ts +56 -0
- package/src/fold/foldable-helpers.ts +81 -1
- package/src/fold-provenance-exclusion.test.ts +186 -0
- package/src/fold-provenance.test.ts +495 -0
- package/src/fold-provenance.ts +441 -0
- package/src/graph-ops.test.ts +2 -2
- package/src/graph-ops.ts +1 -1
- package/src/held-elsewhere.test.ts +102 -0
- package/src/held-elsewhere.ts +114 -0
- package/src/import/ir-utils.ts +11 -1
- package/src/import/parser.ts +35 -0
- package/src/index.ts +3 -0
- package/src/kubectl-context.ts +3 -3
- package/src/lexicon-config.test.ts +5 -5
- package/src/lexicon-config.ts +4 -4
- package/src/lexicon-plugin-helpers.ts +1 -1
- package/src/lexicon.ts +120 -14
- package/src/lifecycle/change-set.test.ts +49 -0
- package/src/lifecycle/change-set.ts +67 -0
- package/src/lifecycle/converge-ledger.ts +1 -1
- package/src/lifecycle/deep-diff.test.ts +267 -14
- package/src/lifecycle/deep-diff.ts +217 -14
- package/src/lifecycle/deep-observe.test.ts +3 -1
- package/src/lifecycle/deep-observe.ts +16 -6
- package/src/lifecycle/gate-ledger.test.ts +74 -2
- package/src/lifecycle/gate-ledger.ts +143 -36
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/release-ledger.ts +2 -2
- package/src/lifecycle/run-ledger.test.ts +268 -0
- package/src/lifecycle/run-ledger.ts +201 -0
- package/src/lifecycle/symptoms.ts +3 -3
- package/src/lint/config.test.ts +82 -1
- package/src/lint/config.ts +112 -3
- package/src/lint/policy.test.ts +1 -1
- package/src/lint/post-synth.test.ts +31 -0
- package/src/lint/post-synth.ts +54 -1
- package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
- package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
- package/src/lint/rules/comp/comp.test.ts +1 -1
- package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +74 -0
- package/src/lint/rules/comp/index.ts +3 -3
- package/src/lint/rules/op/index.ts +23 -0
- package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
- package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
- package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
- package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
- package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
- package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
- package/src/lint/rules/op/support.ts +11 -0
- package/src/lint/suppressions.test.ts +163 -0
- package/src/lint/suppressions.ts +204 -0
- package/src/live-endpoint.ts +1 -1
- package/src/op/activities/activity-contracts.ts +66 -0
- package/src/op/activities/apply.test.ts +567 -0
- package/src/op/activities/apply.ts +662 -0
- package/src/op/activities/build.ts +26 -0
- package/src/op/activities/converge.test.ts +330 -0
- package/src/op/activities/converge.ts +449 -0
- package/src/op/activities/env-teardown.test.ts +187 -0
- package/src/op/activities/env-teardown.ts +171 -0
- package/src/op/activities/guard-validate.test.ts +139 -0
- package/src/op/activities/guard-validate.ts +223 -0
- package/src/op/activities/http-check.test.ts +48 -0
- package/src/op/activities/http-check.ts +55 -0
- package/src/op/activities/index.ts +119 -0
- package/src/op/activities/lexicon-upgrade.test.ts +492 -0
- package/src/op/activities/lexicon-upgrade.ts +673 -0
- package/src/op/activities/lifecycle.ts +85 -0
- package/src/op/activities/pipeline-audit.test.ts +67 -0
- package/src/op/activities/pipeline-audit.ts +201 -0
- package/src/op/activities/policy.test.ts +19 -0
- package/src/op/activities/policy.ts +37 -0
- package/src/op/activities/reconcile.test.ts +81 -0
- package/src/op/activities/reconcile.ts +187 -0
- package/src/op/activities/shell.ts +26 -0
- package/src/op/activities/teardown.ts +22 -0
- package/src/op/activities/util.ts +6 -0
- package/src/op/activities/wait.ts +69 -0
- package/src/op/activities/workflow-audit.test.ts +90 -0
- package/src/op/activities/workflow-audit.ts +203 -0
- package/src/op/activity-contract-registry.test.ts +98 -0
- package/src/op/activity-contract-registry.ts +114 -0
- package/src/op/activity-contract.test.ts +1 -1
- package/src/op/activity-contract.ts +37 -33
- package/src/op/activity-failure.ts +34 -0
- package/src/op/activity-profiles.test.ts +56 -0
- package/src/op/activity-profiles.ts +116 -0
- package/src/op/activity-registry.test.ts +37 -28
- package/src/op/activity-registry.ts +27 -56
- package/src/op/activity-runtime.ts +3 -47
- package/src/op/builders-exports.test.ts +0 -49
- package/src/op/builders-typed.test.ts +164 -0
- package/src/op/builders.ts +123 -39
- package/src/op/composites/apply-op.ts +208 -0
- package/src/op/composites/composites.test.ts +366 -0
- package/src/op/composites/converge-op.test.ts +168 -0
- package/src/op/composites/converge-op.ts +214 -0
- package/src/op/composites/index.ts +26 -0
- package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
- package/src/op/composites/lexicon-upgrade-op.ts +134 -0
- package/src/op/composites/pipeline-audit-op.test.ts +25 -0
- package/src/op/composites/pipeline-audit-op.ts +78 -0
- package/src/op/composites/reconcile-op.test.ts +51 -0
- package/src/op/composites/reconcile-op.ts +107 -0
- package/src/op/composites/watch-op.ts +108 -0
- package/src/op/composites/workflow-audit-op.test.ts +25 -0
- package/src/op/composites/workflow-audit-op.ts +88 -0
- package/src/op/converge-rule.ts +12 -10
- package/src/op/cron.test.ts +128 -0
- package/src/op/cron.ts +149 -0
- package/src/op/discover-root.test.ts +105 -1
- package/src/op/discover.test.ts +6 -2
- package/src/op/discover.ts +84 -16
- package/src/op/duration.ts +21 -0
- package/src/op/effect-step.test.ts +26 -11
- package/src/op/emulator-lifecycle.ts +1 -2
- package/src/op/gate.ts +155 -0
- package/src/op/generate-pipeline.test.ts +65 -3
- package/src/op/generate-pipeline.ts +19 -2
- package/src/op/index.ts +39 -6
- package/src/op/local-executor.test.ts +163 -34
- package/src/op/local-executor.ts +370 -102
- package/src/op/local-output.test.ts +95 -11
- package/src/op/local-output.ts +33 -4
- package/src/op/op-ir.test.ts +281 -0
- package/src/op/op-ir.ts +417 -0
- package/src/op/op-verb-class.ts +19 -5
- package/src/op/op.test.ts +26 -5
- package/src/op/operator.test.ts +175 -6
- package/src/op/operator.ts +63 -16
- package/src/op/receipt-store.ts +1 -1
- package/src/op/resource.ts +20 -2
- package/src/op/runtime.ts +203 -0
- package/src/op/runtimes/local.test.ts +181 -0
- package/src/op/runtimes/local.ts +212 -0
- package/src/op/step-output-ref.ts +15 -13
- package/src/op/types.ts +84 -24
- package/src/ownership.ts +1 -1
- package/src/provenance.test.ts +6 -0
- package/src/provenance.ts +21 -1
- package/src/serializer-walker.test.ts +42 -0
- package/src/serializer-walker.ts +21 -2
- package/src/stack-output.ts +6 -1
- package/dist/cli/handlers/op-progress.d.ts +0 -90
- package/dist/cli/handlers/op-progress.d.ts.map +0 -1
- package/dist/cli/handlers/run-client.d.ts +0 -127
- package/dist/cli/handlers/run-client.d.ts.map +0 -1
- package/dist/cli/handlers/run-report.d.ts +0 -5
- package/dist/cli/handlers/run-report.d.ts.map +0 -1
- package/dist/components/temporal-codegen-loader.d.ts +0 -27
- package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
- package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
- package/src/cli/handlers/op-progress.test.ts +0 -268
- package/src/cli/handlers/op-progress.ts +0 -248
- package/src/cli/handlers/run-client.test.ts +0 -82
- package/src/cli/handlers/run-client.ts +0 -219
- package/src/cli/handlers/run-report.test.ts +0 -62
- package/src/cli/handlers/run-report.ts +0 -122
- package/src/components/temporal-codegen-loader.ts +0 -41
- package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `envTeardown` — the durable form of `chant lifecycle teardown <env> --yes`
|
|
3
|
+
* (#1222).
|
|
4
|
+
*
|
|
5
|
+
* Wraps core's {@link executeTeardown}: enumerate the live resources whose
|
|
6
|
+
* ownership marker carries this project's `ownership.stack` + the requested
|
|
7
|
+
* environment, delete them through each lexicon's `executeTeardown`
|
|
8
|
+
* capability, run core's one bounded retry pass, and fail the activity loudly
|
|
9
|
+
* when any candidate is still `failed` afterwards. Selection is marker-scoped
|
|
10
|
+
* by construction — the same engine the CLI verb drives, reached in-process
|
|
11
|
+
* rather than through a shell.
|
|
12
|
+
*
|
|
13
|
+
* The guards are the CLI's guards, unchanged:
|
|
14
|
+
* - an environment the project does not declare is refused;
|
|
15
|
+
* - a project with no `ownership.stack` is refused — teardown has nothing to
|
|
16
|
+
* select on;
|
|
17
|
+
* - a production-like environment name (`prod`, `production`, `prod-eu`, ...)
|
|
18
|
+
* is refused unless the step's args carry an explicit `confirmProd: true`.
|
|
19
|
+
* An Op is non-interactive, so there is no re-type-the-name prompt to fall
|
|
20
|
+
* back to — the confirmation must be authored into the Op.
|
|
21
|
+
*
|
|
22
|
+
* All three refusals throw before any live read, so a refused teardown
|
|
23
|
+
* touches nothing at all. They are configuration errors, not transient ones —
|
|
24
|
+
* no retry can make `prod` stop looking like production.
|
|
25
|
+
*
|
|
26
|
+
* Distinct from {@link chantTeardown} (`npm run teardown`), which runs
|
|
27
|
+
* whatever script a project wired under that name. This activity is
|
|
28
|
+
* product-agnostic: it imports core only, and core reaches each product
|
|
29
|
+
* through the lexicons the project configures.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { resolve } from "node:path";
|
|
33
|
+
import { loadChantConfig, resolveOwnershipStack, type ChantConfig } from "../../config";
|
|
34
|
+
import { unknownEnvError, isProdLikeEnvironment } from "../../env";
|
|
35
|
+
import { applyLiveEndpoint } from "../../live-endpoint";
|
|
36
|
+
import { executeTeardown, type TeardownReport } from "../../lifecycle/teardown";
|
|
37
|
+
import type { ObservationLexicon } from "../../lexicon";
|
|
38
|
+
|
|
39
|
+
export interface EnvTeardownArgs {
|
|
40
|
+
/** The environment to tear down — the marker env core selects on. */
|
|
41
|
+
env: string;
|
|
42
|
+
/**
|
|
43
|
+
* Required (as an explicit `true`) when `env` looks production-like.
|
|
44
|
+
* The Op's counterpart of the CLI's `--confirm-prod`: activities never
|
|
45
|
+
* prompt, so the confirmation is authored, reviewed, and versioned with
|
|
46
|
+
* the Op instead.
|
|
47
|
+
*/
|
|
48
|
+
confirmProd?: boolean;
|
|
49
|
+
/** Path to the chant project (where chant.config.ts lives). Default: the chant process's cwd. */
|
|
50
|
+
path?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** What a completed (fully successful) teardown reports back to the run. */
|
|
54
|
+
export interface EnvTeardownResult {
|
|
55
|
+
environment: string;
|
|
56
|
+
/** The ownership stack everything was selected on. */
|
|
57
|
+
stack: string;
|
|
58
|
+
deleted: number;
|
|
59
|
+
/** Deliberately refused by a lexicon, with the reason in the report. */
|
|
60
|
+
notPrunable: number;
|
|
61
|
+
/** Planned candidates whose lexicon implements no execution yet. */
|
|
62
|
+
skipped: number;
|
|
63
|
+
/** Kinds that could not be read (#1089) — the plan was incomplete, not clean. */
|
|
64
|
+
holes: number;
|
|
65
|
+
/** The full per-candidate report core produced. */
|
|
66
|
+
report: TeardownReport;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Injectable seams for testing — production loads both from the project. */
|
|
70
|
+
export interface EnvTeardownDeps {
|
|
71
|
+
/** Pre-loaded project config (skips reading chant.config.ts). */
|
|
72
|
+
config?: ChantConfig;
|
|
73
|
+
/** Pre-loaded lexicon plugins (skips resolving + importing lexicon packages). */
|
|
74
|
+
plugins?: ObservationLexicon[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load the project's lexicon plugins through core's own resolution: the
|
|
79
|
+
* `lexicons` list in chant.config, falling back to source-file detection.
|
|
80
|
+
* A dynamic import so this module does not pull the whole CLI surface in at
|
|
81
|
+
* load time — the registry imports every activity module eagerly.
|
|
82
|
+
*/
|
|
83
|
+
async function loadProjectPlugins(projectPath: string): Promise<ObservationLexicon[]> {
|
|
84
|
+
const { loadPlugins, resolveProjectLexicons } = await import("../../cli/plugins");
|
|
85
|
+
return loadPlugins(await resolveProjectLexicons(projectPath));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Tear down one environment's marker-owned resources through the core
|
|
90
|
+
* teardown engine. Uses the `longInfra` profile — deletion waits on the
|
|
91
|
+
* target's own pace. Safe to retry: the engine re-plans from live markers on
|
|
92
|
+
* every run, and a resource already gone is simply no longer a candidate.
|
|
93
|
+
*/
|
|
94
|
+
export async function envTeardown(
|
|
95
|
+
args: EnvTeardownArgs,
|
|
96
|
+
_signal?: AbortSignal,
|
|
97
|
+
deps?: EnvTeardownDeps,
|
|
98
|
+
): Promise<EnvTeardownResult> {
|
|
99
|
+
const projectPath = resolve(args.path ?? ".");
|
|
100
|
+
const config = deps?.config ?? (await loadChantConfig(projectPath)).config;
|
|
101
|
+
|
|
102
|
+
// Refuse an env the project does not declare — a typo here is the
|
|
103
|
+
// difference between tearing down `dev` and tearing down `prod`.
|
|
104
|
+
const envErr = unknownEnvError(args.env, config.environments);
|
|
105
|
+
if (envErr) throw new Error(`envTeardown: ${envErr}`);
|
|
106
|
+
|
|
107
|
+
// Teardown selects on the ownership marker; a project that stamps none has
|
|
108
|
+
// nothing to key on, and "delete what looks like mine" is not a fallback.
|
|
109
|
+
const stack = resolveOwnershipStack(config);
|
|
110
|
+
if (stack === undefined) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
"envTeardown: this project declares no ownership.stack — teardown is marker-scoped and has nothing to select on. " +
|
|
113
|
+
'Set `ownership: { stack: "<name>" }` in chant.config.ts and deploy, so resources carry the marker teardown keys on.',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// The prod guard (#1222). Checked before any live read, so a refused
|
|
118
|
+
// teardown touches nothing at all.
|
|
119
|
+
if (isProdLikeEnvironment(args.env) && args.confirmProd !== true) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`envTeardown: "${args.env}" looks like a production environment — ` +
|
|
122
|
+
"add `confirmProd: true` to the envTeardown step's args to tear it down from an Op.",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const plugins = deps?.plugins ?? (await loadProjectPlugins(projectPath));
|
|
127
|
+
|
|
128
|
+
// #1166 — teardown is a live read and a live write, so an environment's
|
|
129
|
+
// declared endpoint applies here too, unless the ambient shell already set it.
|
|
130
|
+
const reading = plugins.filter((p) => p.teardownOwned || p.describeResources || p.executeTeardown);
|
|
131
|
+
const endpoint = applyLiveEndpoint(config.environments, args.env, reading);
|
|
132
|
+
if (endpoint.notice) console.log(endpoint.notice);
|
|
133
|
+
|
|
134
|
+
let report: TeardownReport;
|
|
135
|
+
try {
|
|
136
|
+
report = await executeTeardown({ environment: args.env, stack, plugins });
|
|
137
|
+
} finally {
|
|
138
|
+
endpoint.restore();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const count = (outcome: string) => report.outcomes.filter((o) => o.outcome === outcome).length;
|
|
142
|
+
const deleted = count("deleted");
|
|
143
|
+
const failed = report.outcomes.filter((o) => o.outcome === "failed");
|
|
144
|
+
const notPrunable = count("not-prunable");
|
|
145
|
+
const skipped = count("skipped");
|
|
146
|
+
|
|
147
|
+
console.log(
|
|
148
|
+
`envTeardown ${args.env} (stack ${stack}): ${deleted} deleted, ${failed.length} failed, ` +
|
|
149
|
+
`${notPrunable} not-prunable, ${skipped} skipped, ${report.plan.holes.length} hole(s)`,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Failures that survived core's bounded retry pass fail the activity —
|
|
153
|
+
// silence is never success. A failed step is what the executor aborts the
|
|
154
|
+
// phase on and what the Op's `onFailure` compensation runs for.
|
|
155
|
+
if (failed.length > 0) {
|
|
156
|
+
const names = failed.map((o) => `${o.lexicon}/${o.name}${o.detail ? ` (${o.detail})` : ""}`).join(", ");
|
|
157
|
+
throw new Error(
|
|
158
|
+
`envTeardown: ${failed.length} of ${report.outcomes.length} candidate(s) still failed after the retry pass: ${names}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
environment: args.env,
|
|
164
|
+
stack,
|
|
165
|
+
deleted,
|
|
166
|
+
notPrunable,
|
|
167
|
+
skipped,
|
|
168
|
+
holes: report.plan.holes.length,
|
|
169
|
+
report,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, test, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import type { NonRetryableActivityError } from "../activity-failure";
|
|
4
|
+
|
|
5
|
+
// guardValidate reaches cfn-guard through the runtime adapter's spawn (not
|
|
6
|
+
// node:child_process), so the I/O seam is the runtime-adapter module — same
|
|
7
|
+
// pattern `lexicons/aws/src/import/live-export-io.test.ts` uses for the aws
|
|
8
|
+
// plugin's own spawn calls. Partial mock (`importOriginal`) rather than a
|
|
9
|
+
// full replacement, for the same reason that test gives: the module is
|
|
10
|
+
// reachable from other real exports this file's own imports touch
|
|
11
|
+
// transitively, so replacing it wholesale would break more than `spawn`.
|
|
12
|
+
const spawnMock = vi.fn();
|
|
13
|
+
vi.mock("../../runtime-adapter", async (importOriginal) => {
|
|
14
|
+
const actual = await importOriginal<typeof import("../../runtime-adapter")>();
|
|
15
|
+
return { ...actual, getRuntime: () => ({ ...actual.getRuntime(), spawn: spawnMock }) };
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
import { guardValidate, parseGuardFindings } from "./guard-validate";
|
|
19
|
+
|
|
20
|
+
const CLEAN = JSON.stringify([{ name: "template.json", not_compliant: [], compliant: [{ Rule: { name: "s3_bucket_ok" } }] }]);
|
|
21
|
+
|
|
22
|
+
const ONE_VIOLATION = JSON.stringify([
|
|
23
|
+
{
|
|
24
|
+
name: "template.json",
|
|
25
|
+
not_compliant: [
|
|
26
|
+
{
|
|
27
|
+
Rule: {
|
|
28
|
+
name: "s3_bucket_public_read_prohibited",
|
|
29
|
+
messages: { error: "PublicAccessBlockConfiguration is missing" },
|
|
30
|
+
checks: [{ Clause: { Unary: { check: { UnResolved: { path: "/Resources/MyBucket/Properties/PublicAccessBlockConfiguration" } } } } }],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
compliant: [],
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
describe("guardValidate activity (#522)", () => {
|
|
39
|
+
beforeEach(() => spawnMock.mockReset());
|
|
40
|
+
|
|
41
|
+
test("spawns cfn-guard validate with -r/-d/--output-format json; template defaults to <path>/template.json", async () => {
|
|
42
|
+
spawnMock.mockResolvedValue({ stdout: CLEAN, stderr: "", exitCode: 0 });
|
|
43
|
+
await guardValidate({ rules: "rules.guard", path: "proj" });
|
|
44
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
45
|
+
const argv = spawnMock.mock.calls[0][0] as string[];
|
|
46
|
+
expect(argv).toEqual([
|
|
47
|
+
"cfn-guard", "validate",
|
|
48
|
+
"-r", "rules.guard",
|
|
49
|
+
"-d", resolve("proj", "template.json"),
|
|
50
|
+
"--output-format", "json",
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("an explicit template/binary override both take", async () => {
|
|
55
|
+
spawnMock.mockResolvedValue({ stdout: CLEAN, stderr: "", exitCode: 0 });
|
|
56
|
+
await guardValidate({ rules: "rules.guard", template: "dist/out.json", binary: "/opt/bin/cfn-guard" });
|
|
57
|
+
const argv = spawnMock.mock.calls[0][0] as string[];
|
|
58
|
+
expect(argv).toEqual(["/opt/bin/cfn-guard", "validate", "-r", "rules.guard", "-d", "dist/out.json", "--output-format", "json"]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("a clean run resolves with no findings", async () => {
|
|
62
|
+
spawnMock.mockResolvedValue({ stdout: CLEAN, stderr: "", exitCode: 0 });
|
|
63
|
+
await expect(guardValidate({ rules: "rules.guard" })).resolves.toEqual({
|
|
64
|
+
findings: [],
|
|
65
|
+
summary: expect.stringContaining("compliant"),
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("a violation throws (non-retryable) and names the rule and the entity", async () => {
|
|
70
|
+
spawnMock.mockResolvedValue({ stdout: ONE_VIOLATION, stderr: "", exitCode: 5 });
|
|
71
|
+
await expect(guardValidate({ rules: "rules.guard" })).rejects.toThrow(
|
|
72
|
+
/s3_bucket_public_read_prohibited.*MyBucket/s,
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("the thrown failure is a non-retryable activity failure typed GuardViolation — same shape policyGate uses for PolicyViolation", async () => {
|
|
77
|
+
spawnMock.mockResolvedValue({ stdout: ONE_VIOLATION, stderr: "", exitCode: 5 });
|
|
78
|
+
try {
|
|
79
|
+
await guardValidate({ rules: "rules.guard" });
|
|
80
|
+
expect.unreachable("guardValidate should have thrown");
|
|
81
|
+
} catch (err) {
|
|
82
|
+
expect((err as NonRetryableActivityError).type).toBe("GuardViolation");
|
|
83
|
+
expect((err as NonRetryableActivityError).nonRetryable).toBe(true);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("cfn-guard producing no output at all (binary missing / failed to run) is a setup error, not a clean pass", async () => {
|
|
88
|
+
spawnMock.mockResolvedValue({ stdout: "", stderr: "spawn cfn-guard ENOENT", exitCode: 1 });
|
|
89
|
+
await expect(guardValidate({ rules: "rules.guard" })).rejects.toThrow(/cfn-guard.*no output/i);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("onFinding other than \"report\" is refused rather than silently ignored", async () => {
|
|
93
|
+
// @ts-expect-error — the type only admits "report"; a dynamically loaded
|
|
94
|
+
// Op config could still pass through an unsupported value at runtime.
|
|
95
|
+
await expect(guardValidate({ rules: "rules.guard", onFinding: "issue" })).rejects.toThrow(
|
|
96
|
+
/onFinding "issue" is not implemented/,
|
|
97
|
+
);
|
|
98
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("parseGuardFindings (#522 — pure mapping, no spawn)", () => {
|
|
103
|
+
test("no not_compliant entries → no findings", () => {
|
|
104
|
+
expect(parseGuardFindings(CLEAN)).toEqual([]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("maps a not_compliant entry to a finding with rule, message, and entity", () => {
|
|
108
|
+
const findings = parseGuardFindings(ONE_VIOLATION);
|
|
109
|
+
expect(findings).toHaveLength(1);
|
|
110
|
+
expect(findings[0]).toMatchObject({
|
|
111
|
+
rule: "s3_bucket_public_read_prohibited",
|
|
112
|
+
severity: "error",
|
|
113
|
+
entity: "MyBucket",
|
|
114
|
+
});
|
|
115
|
+
expect(findings[0].message).toContain("PublicAccessBlockConfiguration is missing");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("a single (non-array) file report is accepted", () => {
|
|
119
|
+
const single = JSON.stringify({ name: "template.json", not_compliant: [{ Rule: { name: "r1" } }] });
|
|
120
|
+
expect(parseGuardFindings(single)).toEqual([{ rule: "r1", severity: "error", message: 'cfn-guard rule "r1" violated' }]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("a rule violation with no recognizable message field still produces a finding, not a silent drop", () => {
|
|
124
|
+
const noMessage = JSON.stringify([{ not_compliant: [{ Rule: { name: "r2" } }] }]);
|
|
125
|
+
expect(parseGuardFindings(noMessage)).toEqual([{ rule: "r2", severity: "error", message: 'cfn-guard rule "r2" violated' }]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("multiple violations across multiple file reports are all collected", () => {
|
|
129
|
+
const multi = JSON.stringify([
|
|
130
|
+
{ name: "a.json", not_compliant: [{ Rule: { name: "r1" } }] },
|
|
131
|
+
{ name: "b.json", not_compliant: [{ Rule: { name: "r2" } }, { Rule: { name: "r3" } }] },
|
|
132
|
+
]);
|
|
133
|
+
expect(parseGuardFindings(multi).map((f) => f.rule)).toEqual(["r1", "r2", "r3"]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("non-JSON stdout throws a clear setup error rather than reporting zero violations", () => {
|
|
137
|
+
expect(() => parseGuardFindings("cfn-guard: error: rules file not found")).toThrow(/did not return parseable JSON/);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* guardValidate — chant #522: reuse an existing CloudFormation Guard rules
|
|
3
|
+
* pack as a first-class Op step, instead of reimplementing compliance packs
|
|
4
|
+
* as native `WAW` rules.
|
|
5
|
+
*
|
|
6
|
+
* chant already emits standard CloudFormation `template.json` (no
|
|
7
|
+
* chant-specific translation), so an org's existing `.guard` files run
|
|
8
|
+
* against it unchanged: this activity is a thin shell around
|
|
9
|
+
* `cfn-guard validate -r <rules> -d <template> --output-format json`, run
|
|
10
|
+
* through the runtime adapter's `spawn` (never `node:child_process`
|
|
11
|
+
* directly — see `lexicons/aws/src/plugin.ts`'s `exportResources` for the
|
|
12
|
+
* same seam, and `../../import/live-export-io.test.ts` for how a test mocks
|
|
13
|
+
* it). `cfn-guard` is an operational dependency, not a `chant build`
|
|
14
|
+
* dependency — nothing here runs inside the pure core.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors `./policy.ts`'s `policyGate`: `report` is the only finding-mode
|
|
17
|
+
* today (the type only admits that one value; `issue`/`pull-request` modes
|
|
18
|
+
* are a follow-up, chant #522), and an error-severity finding throws
|
|
19
|
+
* (a non-retryable activity failure) so the run exits non-zero — the same gate
|
|
20
|
+
* CI relies on to fail the pipeline — and the same activity blocks an `ApplyOp`
|
|
21
|
+
* when placed before it.
|
|
22
|
+
*/
|
|
23
|
+
import { resolve } from "node:path";
|
|
24
|
+
import { nonRetryableFailure } from "../activity-failure";
|
|
25
|
+
|
|
26
|
+
export interface GuardValidateArgs {
|
|
27
|
+
/**
|
|
28
|
+
* Path to a `.guard` rules file or directory, passed to `cfn-guard validate
|
|
29
|
+
* -r`. No chant-specific rule format — these are ordinary cfn-guard rules,
|
|
30
|
+
* the same ones CDK's `CfnGuardValidator` would run.
|
|
31
|
+
*/
|
|
32
|
+
rules: string;
|
|
33
|
+
/**
|
|
34
|
+
* Path to the built CloudFormation template to validate. Default:
|
|
35
|
+
* `<path>/template.json` — the convention `chant build -o template.json`
|
|
36
|
+
* writes to (see the aws lexicon's own examples and the native-output
|
|
37
|
+
* policy docs).
|
|
38
|
+
*/
|
|
39
|
+
template?: string;
|
|
40
|
+
/** Project directory `template` is resolved relative to, when `template` is not given. Default ".". */
|
|
41
|
+
path?: string;
|
|
42
|
+
/**
|
|
43
|
+
* What to produce on a finding. Only `"report"` runs today — it prints a
|
|
44
|
+
* summary and throws on any finding, no external service involved.
|
|
45
|
+
* `issue`/`pull-request` modes (reusing the audit-Op finding-mode
|
|
46
|
+
* plumbing) are left to a follow-up. Default: `"report"`.
|
|
47
|
+
*/
|
|
48
|
+
onFinding?: "report";
|
|
49
|
+
/** Path to (or name of) the `cfn-guard` binary. Default: `"cfn-guard"`, resolved via `PATH`. */
|
|
50
|
+
binary?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** One cfn-guard rule violation, mapped to a chant-shaped diagnostic. */
|
|
54
|
+
export interface GuardFinding {
|
|
55
|
+
/** The cfn-guard rule name that failed. */
|
|
56
|
+
rule: string;
|
|
57
|
+
/** Always `"error"` today — a cfn-guard non-compliant result is a policy violation, the same weight `policyGate` gives a violation. */
|
|
58
|
+
severity: "error";
|
|
59
|
+
/** Human-readable violation message. */
|
|
60
|
+
message: string;
|
|
61
|
+
/** Best-effort CloudFormation logical id the violation is about, when cfn-guard's output names one. */
|
|
62
|
+
entity?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface GuardValidateResult {
|
|
66
|
+
findings: GuardFinding[];
|
|
67
|
+
summary: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type JsonValue = unknown;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Recursively collect message-shaped string values anywhere inside a parsed
|
|
74
|
+
* cfn-guard `not_compliant` entry. cfn-guard's exact JSON nesting for a
|
|
75
|
+
* violation's human message has shifted across versions (Unary/Clause/Check
|
|
76
|
+
* wrappers, `messages.custom_message` vs `messages.error`, …); rather than
|
|
77
|
+
* betting on one fixed path that a version bump silently breaks, this walks
|
|
78
|
+
* the whole entry and takes every message-shaped string it finds — the same
|
|
79
|
+
* "degrade, don't false-negative" instinct `defaultGitlabRefResolver`
|
|
80
|
+
* (`./pipeline-audit.ts`) uses for a resolver failure.
|
|
81
|
+
*/
|
|
82
|
+
function collectMessages(value: JsonValue, out: string[] = [], seen = new Set<string>()): string[] {
|
|
83
|
+
if (typeof value !== "object" || value === null) return out;
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
for (const v of value) collectMessages(v, out, seen);
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
for (const [key, v] of Object.entries(value as Record<string, unknown>)) {
|
|
89
|
+
if (typeof v === "string" && /^(message|custom_message|error)$/i.test(key) && v.trim() && !seen.has(v)) {
|
|
90
|
+
seen.add(v);
|
|
91
|
+
out.push(v);
|
|
92
|
+
} else {
|
|
93
|
+
collectMessages(v, out, seen);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Collect every string value found under a `*path*`-shaped key, anywhere in the entry. */
|
|
100
|
+
function collectPaths(value: JsonValue, out: string[] = []): string[] {
|
|
101
|
+
if (typeof value !== "object" || value === null) return out;
|
|
102
|
+
if (Array.isArray(value)) {
|
|
103
|
+
for (const v of value) collectPaths(v, out);
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
for (const [key, v] of Object.entries(value as Record<string, unknown>)) {
|
|
107
|
+
if (typeof v === "string" && /path/i.test(key)) out.push(v);
|
|
108
|
+
else collectPaths(v, out);
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Best-effort CloudFormation logical id for a violation: cfn-guard reports
|
|
115
|
+
* the offending property as a template path such as
|
|
116
|
+
* `/Resources/MyBucket/Properties/...`. Pull the segment right after
|
|
117
|
+
* `Resources` from the first path-shaped string found, if any.
|
|
118
|
+
*/
|
|
119
|
+
function findEntity(value: JsonValue): string | undefined {
|
|
120
|
+
for (const p of collectPaths(value)) {
|
|
121
|
+
const m = /Resources[./]([A-Za-z0-9]+)/.exec(p);
|
|
122
|
+
if (m) return m[1];
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Map cfn-guard's `--output-format json` report to chant-shaped findings.
|
|
129
|
+
* Exported and tested directly (independent of `spawn`) — same split
|
|
130
|
+
* `collectPipelineRefs`/`pipelineSupplyChainAudit` (`./pipeline-audit.ts`)
|
|
131
|
+
* use between pure mapping and the orchestrating activity.
|
|
132
|
+
*
|
|
133
|
+
* Throws if `stdout` is not parseable JSON — a cfn-guard invocation that
|
|
134
|
+
* didn't run with `--output-format json` (a bad rules path, a version too
|
|
135
|
+
* old to support it) is a setup problem, not "zero violations"; reporting
|
|
136
|
+
* a clean run here would be a false negative.
|
|
137
|
+
*/
|
|
138
|
+
export function parseGuardFindings(stdout: string): GuardFinding[] {
|
|
139
|
+
let parsed: unknown;
|
|
140
|
+
try {
|
|
141
|
+
parsed = JSON.parse(stdout);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`cfn-guard did not return parseable JSON (guardValidate always passes --output-format json) — ` +
|
|
145
|
+
`${err instanceof Error ? err.message : String(err)}\nraw output:\n${stdout}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const fileReports = Array.isArray(parsed) ? parsed : [parsed];
|
|
150
|
+
const findings: GuardFinding[] = [];
|
|
151
|
+
for (const report of fileReports) {
|
|
152
|
+
const notCompliant = (report as { not_compliant?: unknown[] } | null)?.not_compliant ?? [];
|
|
153
|
+
for (const entry of notCompliant) {
|
|
154
|
+
const rule =
|
|
155
|
+
(entry as { Rule?: { name?: string }; name?: string } | null)?.Rule?.name ??
|
|
156
|
+
(entry as { name?: string } | null)?.name ??
|
|
157
|
+
"unknown-rule";
|
|
158
|
+
const messages = collectMessages(entry);
|
|
159
|
+
const message = messages.length > 0 ? `cfn-guard rule "${rule}" violated: ${messages.join("; ")}` : `cfn-guard rule "${rule}" violated`;
|
|
160
|
+
const entity = findEntity(entry);
|
|
161
|
+
findings.push({ rule, severity: "error", message, ...(entity ? { entity } : {}) });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return findings;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function renderSummary(findings: GuardFinding[], template: string, rules: string): string {
|
|
168
|
+
if (findings.length === 0) return `## cfn-guard\n\n${template} is compliant with ${rules}.\n`;
|
|
169
|
+
let out = `## cfn-guard\n\n${findings.length} violation(s) in ${template} against ${rules}:\n\n`;
|
|
170
|
+
out += "| Rule | Entity | Message |\n|---|---|---|\n";
|
|
171
|
+
for (const f of findings) out += `| ${f.rule} | ${f.entity ?? ""} | ${f.message} |\n`;
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Run an external CloudFormation Guard rules pack against the project's
|
|
177
|
+
* built CloudFormation template. `report` mode: prints a summary, and
|
|
178
|
+
* throws (non-retryable) when any rule is violated, so the local executor
|
|
179
|
+
* (`chant run`) exits non-zero and CI fails the pipeline — the same shape
|
|
180
|
+
* `policyGate` uses to block an apply. A clean run passes through.
|
|
181
|
+
*
|
|
182
|
+
* A plain activity on every runtime, not a `gate` step.
|
|
183
|
+
*/
|
|
184
|
+
export async function guardValidate(args: GuardValidateArgs, _signal?: AbortSignal): Promise<GuardValidateResult> {
|
|
185
|
+
const mode = args.onFinding ?? "report";
|
|
186
|
+
if (mode !== "report") {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`guardValidate: onFinding "${mode}" is not implemented — only "report" runs today ` +
|
|
189
|
+
`(chant #522 leaves issue/pull-request modes to a follow-up).`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const projectPath = args.path ?? ".";
|
|
194
|
+
const template = args.template ?? resolve(projectPath, "template.json");
|
|
195
|
+
const binary = args.binary ?? "cfn-guard";
|
|
196
|
+
|
|
197
|
+
const { getRuntime } = await import("../../runtime-adapter");
|
|
198
|
+
const rt = getRuntime();
|
|
199
|
+
const result = await rt.spawn([binary, "validate", "-r", args.rules, "-d", template, "--output-format", "json"]);
|
|
200
|
+
|
|
201
|
+
if (result.stdout.trim() === "" && result.stderr.trim() !== "") {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`cfn-guard ("${binary}") produced no output — ${result.stderr.trim()}\n` +
|
|
204
|
+
`Install cfn-guard (https://github.com/aws-cloudformation/cloudformation-guard) or pass { binary } ` +
|
|
205
|
+
`to point at an existing install.`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const findings = parseGuardFindings(result.stdout);
|
|
210
|
+
const summary = renderSummary(findings, template, args.rules);
|
|
211
|
+
|
|
212
|
+
if (findings.length > 0) {
|
|
213
|
+
console.log(summary);
|
|
214
|
+
const detail = findings.map((f) => `[${f.rule}]${f.entity ? ` ${f.entity}:` : ""} ${f.message}`).join("; ");
|
|
215
|
+
throw nonRetryableFailure(
|
|
216
|
+
`cfn-guard blocked the build — ${findings.length} violation(s) against ${args.rules}: ${detail}`,
|
|
217
|
+
"GuardViolation",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log(`[guard] ${template} is compliant with ${args.rules}`);
|
|
222
|
+
return { findings, summary };
|
|
223
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { httpCheck, statusOk, type HttpFetch } from "./http-check";
|
|
3
|
+
|
|
4
|
+
const res = (status: number, body = "") => ({ status, text: async () => body });
|
|
5
|
+
|
|
6
|
+
describe("statusOk (#typed-verify)", () => {
|
|
7
|
+
test("any 2xx when no expectation, exact match otherwise", () => {
|
|
8
|
+
expect(statusOk(200)).toBe(true);
|
|
9
|
+
expect(statusOk(204)).toBe(true);
|
|
10
|
+
expect(statusOk(404)).toBe(false);
|
|
11
|
+
expect(statusOk(404, 404)).toBe(true);
|
|
12
|
+
expect(statusOk(200, 201)).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("httpCheck (#typed-verify)", () => {
|
|
17
|
+
test("passes on 2xx and returns the status", async () => {
|
|
18
|
+
const fetchFn: HttpFetch = async () => res(200);
|
|
19
|
+
expect(await httpCheck({ url: "http://x" }, undefined, fetchFn)).toEqual({ status: 200 });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("throws when the status doesn't match", async () => {
|
|
23
|
+
const fetchFn: HttpFetch = async () => res(500, "boom");
|
|
24
|
+
await expect(httpCheck({ url: "http://x" }, undefined, fetchFn)).rejects.toThrow(/status 500 \(want 2xx\)/);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("checks a body substring when `contains` is set", async () => {
|
|
28
|
+
const fetchFn: HttpFetch = async () => res(200, '{"name":"trio-bucket"}');
|
|
29
|
+
expect(await httpCheck({ url: "http://x", contains: "trio-bucket" }, undefined, fetchFn)).toEqual({ status: 200 });
|
|
30
|
+
await expect(
|
|
31
|
+
httpCheck({ url: "http://x", contains: "missing" }, undefined, fetchFn),
|
|
32
|
+
).rejects.toThrow(/body missing "missing"/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("retries then succeeds", async () => {
|
|
36
|
+
let n = 0;
|
|
37
|
+
const fetchFn: HttpFetch = async () => (++n < 3 ? res(503) : res(200));
|
|
38
|
+
expect(await httpCheck({ url: "http://x", retries: 3, intervalMs: 1 }, undefined, fetchFn)).toEqual({ status: 200 });
|
|
39
|
+
expect(n).toBe(3);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("surfaces a fetch (connection) error", async () => {
|
|
43
|
+
const fetchFn: HttpFetch = async () => {
|
|
44
|
+
throw new Error("ECONNREFUSED");
|
|
45
|
+
};
|
|
46
|
+
await expect(httpCheck({ url: "http://x" }, undefined, fetchFn)).rejects.toThrow(/ECONNREFUSED/);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { sleep } from "../activity-runtime";
|
|
2
|
+
|
|
3
|
+
export interface HttpCheckArgs {
|
|
4
|
+
/** URL to GET (or `method`). */
|
|
5
|
+
url: string;
|
|
6
|
+
/** HTTP method. Default: `GET`. */
|
|
7
|
+
method?: string;
|
|
8
|
+
/** Exact expected status. Default: any 2xx. */
|
|
9
|
+
status?: number;
|
|
10
|
+
/** Substring the response body must contain. */
|
|
11
|
+
contains?: string;
|
|
12
|
+
/** Retries before failing, for a resource that may lag. Default: `0`. */
|
|
13
|
+
retries?: number;
|
|
14
|
+
/** Delay between retries in ms. Default: `1000`. */
|
|
15
|
+
intervalMs?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Injectable fetch — the response shape `httpCheck` needs. Defaults to global `fetch`. */
|
|
19
|
+
export type HttpFetch = (url: string, init?: { method?: string; signal?: AbortSignal }) => Promise<{ status: number; text(): Promise<string> }>;
|
|
20
|
+
|
|
21
|
+
/** Whether a status satisfies the expectation (exact if given, else any 2xx). Pure. */
|
|
22
|
+
export function statusOk(status: number, expected?: number): boolean {
|
|
23
|
+
return expected === undefined ? status >= 200 && status < 300 : status === expected;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Assert an HTTP endpoint responds as expected — a typed verify step, the modeled
|
|
28
|
+
* replacement for `shell("curl -fs ...")`. Fails (throwing) if the status or body
|
|
29
|
+
* doesn't match after the allowed retries. `fetchFn` is injectable for tests.
|
|
30
|
+
* Uses fastIdempotent profile — 5m timeout.
|
|
31
|
+
*/
|
|
32
|
+
export async function httpCheck(args: HttpCheckArgs, signal?: AbortSignal, fetchFn: HttpFetch = fetch): Promise<{ status: number }> {
|
|
33
|
+
const retries = args.retries ?? 0;
|
|
34
|
+
const intervalMs = args.intervalMs ?? 1_000;
|
|
35
|
+
let lastErr = "";
|
|
36
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
37
|
+
if (signal?.aborted) throw new Error("httpCheck aborted");
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetchFn(args.url, { method: args.method ?? "GET", signal });
|
|
40
|
+
const body = args.contains !== undefined ? await res.text() : "";
|
|
41
|
+
if (!statusOk(res.status, args.status)) {
|
|
42
|
+
lastErr = `status ${res.status} (want ${args.status ?? "2xx"})`;
|
|
43
|
+
} else if (args.contains !== undefined && !body.includes(args.contains)) {
|
|
44
|
+
lastErr = `body missing "${args.contains}"`;
|
|
45
|
+
} else {
|
|
46
|
+
console.log(`httpCheck OK: ${args.url} (${res.status})`);
|
|
47
|
+
return { status: res.status };
|
|
48
|
+
}
|
|
49
|
+
} catch (e) {
|
|
50
|
+
lastErr = e instanceof Error ? e.message : String(e);
|
|
51
|
+
}
|
|
52
|
+
if (attempt < retries) await sleep(intervalMs, signal);
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`httpCheck failed for ${args.url}: ${lastErr}`);
|
|
55
|
+
}
|