@intentius/chant 0.57.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/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/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/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/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/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/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
package/src/audit/secrets.ts
CHANGED
|
@@ -173,7 +173,13 @@ export function parseSecretsConfig(content: string): SecretsScanOptions {
|
|
|
173
173
|
|
|
174
174
|
// ── Shannon entropy ─────────────────────────────────────────────────────
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Shannon entropy in bits per character. Exported so a lexicon's own
|
|
178
|
+
* secret-shape heuristic can score a candidate on the same scale this
|
|
179
|
+
* module's SEC010 catch-all uses (the terraform lexicon's
|
|
180
|
+
* `lint/secret-shape.ts`, #2110) instead of inventing a second one.
|
|
181
|
+
*/
|
|
182
|
+
export function shannonEntropy(s: string): number {
|
|
177
183
|
const freq = new Map<string, number>();
|
|
178
184
|
for (const ch of s) freq.set(ch, (freq.get(ch) ?? 0) + 1);
|
|
179
185
|
let entropy = 0;
|
|
@@ -188,8 +194,14 @@ function shannonEntropy(s: string): number {
|
|
|
188
194
|
|
|
189
195
|
const PLACEHOLDER_RE = /example|placeholder|dummy|changeme|change-me|redacted|sample|fixme|todo|xxxxxxxx|your[-_]?(api[-_]?)?(key|token|secret)|fake[-_]?(key|token|secret)|test[-_]?(key|token|secret)/i;
|
|
190
196
|
|
|
191
|
-
/**
|
|
192
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Cheap, format-agnostic "this doesn't look like a real secret" filter.
|
|
199
|
+
* Exported for the same reason {@link shannonEntropy} is: the placeholder
|
|
200
|
+
* vocabulary a scanner has to ignore (`changeme`, `example`, `your-api-key`,
|
|
201
|
+
* a run of one repeated character) is the same wherever the candidate came
|
|
202
|
+
* from, and a second copy of this list would drift from this one.
|
|
203
|
+
*/
|
|
204
|
+
export function looksLikePlaceholder(value: string): boolean {
|
|
193
205
|
if (PLACEHOLDER_RE.test(value)) return true;
|
|
194
206
|
const uniq = new Set(value.toLowerCase()).size;
|
|
195
207
|
if (value.length > 8 && uniq <= 2) return true; // e.g. "0000000000000000"
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { describe, expect, test } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
auditTerraformState,
|
|
7
|
+
gitignoreCoversTerraformState,
|
|
8
|
+
isTerraformStateFile,
|
|
9
|
+
isTerraformStatePath,
|
|
10
|
+
isTerraformWorkDir,
|
|
11
|
+
type ScannableFile,
|
|
12
|
+
} from "./terraform-state";
|
|
13
|
+
import { collectCandidates } from "./discover";
|
|
14
|
+
import { RULE_CATALOG, RULE_CATEGORY } from "./catalog";
|
|
15
|
+
|
|
16
|
+
const file = (path: string): ScannableFile => ({ path, content: "" });
|
|
17
|
+
|
|
18
|
+
function tmpRepo(): string {
|
|
19
|
+
return mkdtempSync(join(tmpdir(), "chant-tf023-"));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("the TF023 path detectors", () => {
|
|
23
|
+
test.each([
|
|
24
|
+
"terraform.tfstate",
|
|
25
|
+
"terraform.tfstate.backup",
|
|
26
|
+
"infra/prod/terraform.tfstate",
|
|
27
|
+
"infra/prod.tfstate",
|
|
28
|
+
"infra/prod.tfstate.backup",
|
|
29
|
+
])("%s is state", (path) => {
|
|
30
|
+
expect(isTerraformStateFile(path)).toBe(true);
|
|
31
|
+
expect(isTerraformStatePath(path)).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test.each(["main.tf", "terraform.tfvars", "state.json", "docs/tfstate.md"])("%s is not", (path) => {
|
|
35
|
+
expect(isTerraformStateFile(path)).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("the working directory is matched as a path segment, at any depth", () => {
|
|
39
|
+
expect(isTerraformWorkDir(".terraform")).toBe(true);
|
|
40
|
+
expect(isTerraformWorkDir(".terraform/providers/registry.terraform.io/hashicorp/aws/5.0.0")).toBe(true);
|
|
41
|
+
expect(isTerraformWorkDir("infra/prod/.terraform")).toBe(true);
|
|
42
|
+
expect(isTerraformWorkDir("infra/terraform/main.tf")).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("auditTerraformState", () => {
|
|
47
|
+
test("reports a committed state file as an error, without reading it", () => {
|
|
48
|
+
const findings = auditTerraformState([file("infra/prod/terraform.tfstate")]);
|
|
49
|
+
expect(findings).toHaveLength(1);
|
|
50
|
+
expect(findings[0].checkId).toBe("TF023");
|
|
51
|
+
expect(findings[0].severity).toBe("error");
|
|
52
|
+
expect(findings[0].lexicon).toBe("terraform");
|
|
53
|
+
expect(findings[0].file).toBe("infra/prod/terraform.tfstate");
|
|
54
|
+
expect(findings[0].message).toContain("secret store");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("reports a backup state file too", () => {
|
|
58
|
+
expect(auditTerraformState([file("terraform.tfstate.backup")])).toHaveLength(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("reports a `.terraform` directory once, not once per file inside it", () => {
|
|
62
|
+
const findings = auditTerraformState([
|
|
63
|
+
file(".terraform"),
|
|
64
|
+
file(".terraform/modules/modules.json"),
|
|
65
|
+
file(".terraform/providers/registry.terraform.io/hashicorp/aws/5.0.0/terraform-provider-aws"),
|
|
66
|
+
]);
|
|
67
|
+
expect(findings).toHaveLength(1);
|
|
68
|
+
expect(findings[0].file).toBe(".terraform");
|
|
69
|
+
expect(findings[0].entity).toBe(".terraform");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("reports each root's own working directory separately", () => {
|
|
73
|
+
const findings = auditTerraformState([file("infra/prod/.terraform"), file("infra/staging/.terraform")]);
|
|
74
|
+
expect(findings.map((f) => f.file)).toEqual(["infra/prod/.terraform", "infra/staging/.terraform"]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("says nothing about a repository with neither", () => {
|
|
78
|
+
expect(auditTerraformState([{ path: "main.tf", content: 'resource "null_resource" "a" {}' }])).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("is catalogued in core, as a merge-worthy security rule", () => {
|
|
82
|
+
expect(RULE_CATALOG.TF023.tier).toBe("merge-worthy");
|
|
83
|
+
expect(RULE_CATALOG.TF023.category).toBe("security");
|
|
84
|
+
expect(RULE_CATEGORY.TF023).toBe("security");
|
|
85
|
+
expect(RULE_CATALOG.TF023.authority?.[0].url).toContain("#gitignore");
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("gitignoreCoversTerraformState", () => {
|
|
90
|
+
test.each([
|
|
91
|
+
["*.tfstate", "terraform.tfstate"],
|
|
92
|
+
["*.tfstate.*", "terraform.tfstate.backup"],
|
|
93
|
+
[".terraform/", ".terraform"],
|
|
94
|
+
[".terraform*", ".terraform"],
|
|
95
|
+
["/terraform.tfstate", "terraform.tfstate"],
|
|
96
|
+
["**/.terraform", "infra/prod/.terraform"],
|
|
97
|
+
["# comment\n\n*.tfstate\n", "infra/prod/terraform.tfstate"],
|
|
98
|
+
])("%s covers %s", (gitignore, path) => {
|
|
99
|
+
expect(gitignoreCoversTerraformState(gitignore, path)).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test.each([
|
|
103
|
+
["node_modules/\ndist/", "terraform.tfstate"],
|
|
104
|
+
["*.tfvars", "terraform.tfstate"],
|
|
105
|
+
["!terraform.tfstate", "terraform.tfstate"],
|
|
106
|
+
])("%s does not cover %s", (gitignore, path) => {
|
|
107
|
+
expect(gitignoreCoversTerraformState(gitignore, path)).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("discovery hands TF023 its paths, and nothing else changes", () => {
|
|
112
|
+
test("a state file becomes a candidate with no content, so nothing reads it", () => {
|
|
113
|
+
const dir = tmpRepo();
|
|
114
|
+
writeFileSync(join(dir, "main.tf"), 'resource "null_resource" "a" {}\n');
|
|
115
|
+
writeFileSync(join(dir, "terraform.tfstate"), JSON.stringify({ version: 4, resources: [] }));
|
|
116
|
+
const candidates = collectCandidates(dir);
|
|
117
|
+
const state = candidates.find((c) => c.path === "terraform.tfstate");
|
|
118
|
+
expect(state).toBeDefined();
|
|
119
|
+
expect(state!.content).toBe("");
|
|
120
|
+
expect(auditTerraformState(candidates)).toHaveLength(1);
|
|
121
|
+
rmSync(dir, { recursive: true, force: true });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("the walk records `.terraform` without descending into it", () => {
|
|
125
|
+
const dir = tmpRepo();
|
|
126
|
+
writeFileSync(join(dir, "main.tf"), 'resource "null_resource" "a" {}\n');
|
|
127
|
+
mkdirSync(join(dir, ".terraform", "providers"), { recursive: true });
|
|
128
|
+
writeFileSync(join(dir, ".terraform", "providers", "huge.json"), "{}");
|
|
129
|
+
const candidates = collectCandidates(dir);
|
|
130
|
+
expect(candidates.map((c) => c.path).sort()).toEqual([".terraform", "main.tf"]);
|
|
131
|
+
expect(auditTerraformState(candidates)).toHaveLength(1);
|
|
132
|
+
rmSync(dir, { recursive: true, force: true });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("a gitignored state file is on disk, not in the repository, so it is dropped", () => {
|
|
136
|
+
const dir = tmpRepo();
|
|
137
|
+
writeFileSync(join(dir, ".gitignore"), ".terraform/\n*.tfstate\n*.tfstate.*\n");
|
|
138
|
+
writeFileSync(join(dir, "main.tf"), 'resource "null_resource" "a" {}\n');
|
|
139
|
+
writeFileSync(join(dir, "terraform.tfstate"), "{}");
|
|
140
|
+
mkdirSync(join(dir, ".terraform"), { recursive: true });
|
|
141
|
+
expect(auditTerraformState(collectCandidates(dir))).toEqual([]);
|
|
142
|
+
rmSync(dir, { recursive: true, force: true });
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF023 (#2110): Terraform state, or the `.terraform/` working directory,
|
|
3
|
+
* committed to the repository.
|
|
4
|
+
*
|
|
5
|
+
* Every other rule in the TF family is a post-synth check over a parsed root
|
|
6
|
+
* module, contributed by the terraform lexicon. This one is not about HCL at
|
|
7
|
+
* all: the finding is that a path exists in version control, so there is
|
|
8
|
+
* nothing for `chant build` to check and nothing for the lexicon's parser to
|
|
9
|
+
* read. It therefore takes the shape `secrets.ts` established and `wrangler.ts`
|
|
10
|
+
* and `nginx.ts` copied, a path detector plus a check over the discovered file
|
|
11
|
+
* list, called from the audit CLI beside `scanForSecrets`, with its catalog
|
|
12
|
+
* entry in core's own `RULE_CATALOG` (./catalog.ts) rather than a lexicon's.
|
|
13
|
+
* It runs whether or not the terraform lexicon is installed.
|
|
14
|
+
*
|
|
15
|
+
* A state file is a committed secret store: it records every attribute of every
|
|
16
|
+
* managed resource, including the ones providers mark sensitive, in plaintext
|
|
17
|
+
* JSON. `.terraform/` is milder (providers and vendored modules) but is still
|
|
18
|
+
* hundreds of megabytes of machine-generated content, and it can hold a
|
|
19
|
+
* `terraform.tfstate` of its own for the workspace-local backend.
|
|
20
|
+
*
|
|
21
|
+
* Pure: no fs, no network, no Node-only global, so it is Workers-safe (epic #350). The
|
|
22
|
+
* walk that decides which paths reach here is `discover.ts`'s
|
|
23
|
+
* `collectCandidates`, which is also where a locally ignored path is dropped:
|
|
24
|
+
* see `isTerraformStatePath` there and `gitignoreCoversTerraformState` below.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { AuditFinding } from "./core";
|
|
28
|
+
|
|
29
|
+
/** The minimal file shape the check needs (matches `discover.ts`'s `RepoFile`). */
|
|
30
|
+
export interface ScannableFile {
|
|
31
|
+
path: string;
|
|
32
|
+
content: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** `terraform.tfstate`, `terraform.tfstate.backup`, and any other `*.tfstate`. */
|
|
36
|
+
export function isTerraformStateFile(path: string): boolean {
|
|
37
|
+
const name = path.split("/").pop() ?? path;
|
|
38
|
+
return /\.tfstate(\.backup)?$/i.test(name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The `.terraform` working directory itself, or anything inside it. */
|
|
42
|
+
export function isTerraformWorkDir(path: string): boolean {
|
|
43
|
+
const parts = path.split("/");
|
|
44
|
+
return parts.includes(".terraform");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Either of the two shapes TF023 reports. */
|
|
48
|
+
export function isTerraformStatePath(path: string): boolean {
|
|
49
|
+
return isTerraformStateFile(path) || isTerraformWorkDir(path);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Does a `.gitignore` body already exclude Terraform state? Deliberately
|
|
54
|
+
* narrow: it recognises the handful of patterns the style guide's own
|
|
55
|
+
* `.gitignore` section and `github/gitignore`'s `Terraform.gitignore` use
|
|
56
|
+
* (`*.tfstate`, `*.tfstate.*`, `.terraform/`, `.terraform*`, with an optional
|
|
57
|
+
* leading slash or a leading double-star), not the whole gitignore grammar. A repository that
|
|
58
|
+
* writes something more exotic gets a finding it can suppress; the alternative,
|
|
59
|
+
* a partial gitignore engine, would be wrong in quieter ways.
|
|
60
|
+
*
|
|
61
|
+
* Used by the local walk only (`discover.ts`), where "on disk" and "tracked by
|
|
62
|
+
* git" are different questions and nearly every Terraform working tree has an
|
|
63
|
+
* ignored `.terraform/` in it. A fetched repository's file list is already
|
|
64
|
+
* exactly the tracked files, so nothing filters it.
|
|
65
|
+
*/
|
|
66
|
+
function escapeRegExp(literal: string): string {
|
|
67
|
+
return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function gitignoreCoversTerraformState(gitignore: string, path: string): boolean {
|
|
71
|
+
const name = path.split("/").pop() ?? path;
|
|
72
|
+
for (const raw of gitignore.split("\n")) {
|
|
73
|
+
const line = raw.trim();
|
|
74
|
+
if (line === "" || line.startsWith("#") || line.startsWith("!")) continue;
|
|
75
|
+
const pattern = line.replace(/^\*\*\//, "").replace(/^\//, "").replace(/\/$/, "");
|
|
76
|
+
if (pattern === "" || pattern.includes("/")) continue;
|
|
77
|
+
// `*` matches within one path segment, as git's own glob does. The pattern
|
|
78
|
+
// is compared against the file's own name and against every segment of its
|
|
79
|
+
// path, which is what makes `.terraform/` match `infra/prod/.terraform`.
|
|
80
|
+
const re = new RegExp(`^${pattern.split("*").map(escapeRegExp).join("[^/]*")}$`);
|
|
81
|
+
if (re.test(name) || path.split("/").some((segment) => re.test(segment))) return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Report TF023 for every committed state file and `.terraform/` directory in
|
|
88
|
+
* the file list. One finding per path, and one for the `.terraform` directory
|
|
89
|
+
* as a whole rather than per file inside it: a vendored provider binary is not
|
|
90
|
+
* a separate finding from the directory that holds it.
|
|
91
|
+
*/
|
|
92
|
+
export function auditTerraformState(files: ScannableFile[]): AuditFinding[] {
|
|
93
|
+
const findings: AuditFinding[] = [];
|
|
94
|
+
const workDirs = new Set<string>();
|
|
95
|
+
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
if (isTerraformWorkDir(file.path)) {
|
|
98
|
+
const parts = file.path.split("/");
|
|
99
|
+
const dir = parts.slice(0, parts.indexOf(".terraform") + 1).join("/");
|
|
100
|
+
if (workDirs.has(dir)) continue;
|
|
101
|
+
workDirs.add(dir);
|
|
102
|
+
findings.push({
|
|
103
|
+
checkId: "TF023",
|
|
104
|
+
severity: "warning",
|
|
105
|
+
message:
|
|
106
|
+
`\`${dir}\` is committed to the repository. Terraform's working directory holds downloaded ` +
|
|
107
|
+
"providers and vendored child modules, and the local backend keeps its state there too. " +
|
|
108
|
+
"Delete it from version control and add `.terraform/` to .gitignore.",
|
|
109
|
+
file: dir,
|
|
110
|
+
lexicon: "terraform",
|
|
111
|
+
entity: ".terraform",
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!isTerraformStateFile(file.path)) continue;
|
|
116
|
+
findings.push({
|
|
117
|
+
checkId: "TF023",
|
|
118
|
+
severity: "error",
|
|
119
|
+
message:
|
|
120
|
+
`\`${file.path}\` is committed to the repository. A state file records every attribute of every ` +
|
|
121
|
+
"managed resource in plaintext, including the ones providers mark sensitive, so a committed " +
|
|
122
|
+
"state file is a committed secret store. Remove it from version control, add `*.tfstate` and " +
|
|
123
|
+
"`*.tfstate.backup` to .gitignore, move the state to a remote backend, and rotate anything it held.",
|
|
124
|
+
file: file.path,
|
|
125
|
+
lexicon: "terraform",
|
|
126
|
+
entity: file.path.split("/").pop() ?? file.path,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return findings;
|
|
131
|
+
}
|
package/src/build.ts
CHANGED
|
@@ -3,12 +3,14 @@ import type { Serializer, SerializerResult } from "./serializer";
|
|
|
3
3
|
import type { OwnershipMarker } from "./ownership";
|
|
4
4
|
import type { BuildError, DiscoveryErrorType } from "./errors";
|
|
5
5
|
import type { IntrinsicDef, BuildRootContribution, BuildRootContributor } from "./lexicon";
|
|
6
|
-
import type
|
|
6
|
+
import { getProvenance, type BuildParamProvenance } from "./provenance";
|
|
7
|
+
import { foldProvenanceOfEntities, type FoldProvenance } from "./fold-provenance";
|
|
7
8
|
import { DiscoveryError, BuildError as BuildErrorClass } from "./errors";
|
|
8
9
|
import { LexiconOutput, isLexiconOutput } from "./lexicon-output";
|
|
9
10
|
import { isSecretDeclaration } from "./secret-provenance";
|
|
10
11
|
import { splitReceiptEntities } from "./effect-receipt";
|
|
11
12
|
import { isScenario } from "./lifecycle/scenario";
|
|
13
|
+
import { isOpEntity } from "./op/resource";
|
|
12
14
|
import { AttrRef } from "./attrref";
|
|
13
15
|
import { isAttrRefLike } from "./utils";
|
|
14
16
|
import { isChildProject, type ChildProjectInstance } from "./child-project";
|
|
@@ -282,6 +284,22 @@ export interface BuildResult {
|
|
|
282
284
|
* supplies none.
|
|
283
285
|
*/
|
|
284
286
|
buildParams: BuildParamProvenance[];
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* chant #2161 — emitted property path to origin, per entity: which composite
|
|
290
|
+
* parameter produced the field, which literal the composite fixes, which the
|
|
291
|
+
* author declared directly, and which this build could not attribute.
|
|
292
|
+
*
|
|
293
|
+
* Beside the output on purpose, and never inside it. Nothing here is passed
|
|
294
|
+
* to a serializer, so it cannot reach the document an applier writes; see
|
|
295
|
+
* ./fold-provenance.ts's module doc and `fold-provenance-exclusion.test.ts`.
|
|
296
|
+
*
|
|
297
|
+
* Every entity with properties appears. The origins are as fine as the build
|
|
298
|
+
* managed: a run build, or a fold build over a factory outside the
|
|
299
|
+
* interpretable subset, records `unknown` for a composite's fields rather
|
|
300
|
+
* than guessing.
|
|
301
|
+
*/
|
|
302
|
+
foldProvenance: FoldProvenance;
|
|
285
303
|
}
|
|
286
304
|
|
|
287
305
|
/**
|
|
@@ -308,6 +326,13 @@ export function partitionByLexicon(
|
|
|
308
326
|
// Plan scenarios (#1292) are serializer-neutral the same way: a checkable
|
|
309
327
|
// expectation the CLI reads off the entity map, never output.
|
|
310
328
|
if (isScenario(entity)) continue;
|
|
329
|
+
// Op declarations (#2118) too. An Op's build output is its `op.json` IR,
|
|
330
|
+
// and core writes that itself from the entity map (`./cli/commands/build.ts`)
|
|
331
|
+
// rather than routing it through a lexicon serializer — the Op model, the
|
|
332
|
+
// executor and the IR are all core's, and no lexicon renders an Op entity
|
|
333
|
+
// into its own manifest. Keeping them out of every partition means no
|
|
334
|
+
// "No serializer found" warning fires for core's own `chant` lexicon.
|
|
335
|
+
if (isOpEntity(entity)) continue;
|
|
311
336
|
const lexicon = entity.lexicon;
|
|
312
337
|
if (!partitions.has(lexicon)) {
|
|
313
338
|
partitions.set(lexicon, new Map());
|
|
@@ -852,6 +877,11 @@ async function buildFromDiscoveryResult(
|
|
|
852
877
|
foldDecisions: discoveryResult.foldDecisions,
|
|
853
878
|
lexiconVersions: { ...(options?.lexiconVersions ?? {}) },
|
|
854
879
|
buildParams: options?.buildParams ?? [],
|
|
880
|
+
// chant #2161 — read off the entities AFTER every step that can change
|
|
881
|
+
// them (the shared-props merge in `expandComposite`, the build-root merge
|
|
882
|
+
// above), and deliberately after Step 7, so nothing about computing it can
|
|
883
|
+
// reach a serializer.
|
|
884
|
+
foldProvenance: foldProvenanceOfEntities(discoveryResult.entities, getProvenance),
|
|
855
885
|
};
|
|
856
886
|
}
|
|
857
887
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
NO_CLAIMED_FIELDS,
|
|
4
|
+
claimedFieldPaths,
|
|
5
|
+
claimedFieldsFromPaths,
|
|
6
|
+
claimedFieldsOfProps,
|
|
7
|
+
claimedFieldsOfTree,
|
|
8
|
+
classifyLiveField,
|
|
9
|
+
heldBy,
|
|
10
|
+
isClaimed,
|
|
11
|
+
} from "./claimed-fields";
|
|
12
|
+
import { UNRESOLVED, type DeepNormalizationHooks } from "./deep-observation";
|
|
13
|
+
|
|
14
|
+
describe("claimedFieldsOfProps", () => {
|
|
15
|
+
test("walks props to leaf paths in the diff's grammar", () => {
|
|
16
|
+
const claimed = claimedFieldsOfProps(
|
|
17
|
+
{ spec: { replicas: 2, template: { spec: { containers: [{ name: "app", image: "a:1" }] } } } },
|
|
18
|
+
{ entityType: "K8s::Apps::Deployment" },
|
|
19
|
+
);
|
|
20
|
+
expect(claimedFieldPaths(claimed)).toEqual([
|
|
21
|
+
"spec.replicas",
|
|
22
|
+
"spec.template.spec.containers[0].image",
|
|
23
|
+
"spec.template.spec.containers[0].name",
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("addresses set-like arrays by the lexicon's own order key", () => {
|
|
28
|
+
const hooks: DeepNormalizationHooks = {
|
|
29
|
+
orderKey: (el) => (el.pattern === "Tags" ? String((el.element as { Key: string }).Key) : undefined),
|
|
30
|
+
};
|
|
31
|
+
const claimed = claimedFieldsOfProps(
|
|
32
|
+
{ Tags: [{ Key: "env", Value: "prod" }] },
|
|
33
|
+
{ entityType: "AWS::S3::Bucket", hooks },
|
|
34
|
+
);
|
|
35
|
+
expect(claimedFieldPaths(claimed)).toEqual(["Tags[#env].Key", "Tags[#env].Value"]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("a pruned property is not claimed", () => {
|
|
39
|
+
// The hooks decide what is even in the tree, so the claim inherits them
|
|
40
|
+
// rather than second-guessing them: a field the lexicon subtracts on both
|
|
41
|
+
// sides is not a field chant is asserting anything about.
|
|
42
|
+
const hooks: DeepNormalizationHooks = { prune: (node) => node.pattern.startsWith("status") };
|
|
43
|
+
const claimed = claimedFieldsOfProps(
|
|
44
|
+
{ spec: { replicas: 2 }, status: { readyReplicas: 2 } },
|
|
45
|
+
{ entityType: "T", hooks },
|
|
46
|
+
);
|
|
47
|
+
expect(claimedFieldPaths(claimed)).toEqual(["spec.replicas"]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("an unevaluated intrinsic is still claimed", () => {
|
|
51
|
+
// chant set the field; it just cannot say what the reference resolves to.
|
|
52
|
+
const claimed = claimedFieldsOfProps({ BucketName: UNRESOLVED }, { entityType: "T" });
|
|
53
|
+
expect(isClaimed(claimed, "BucketName")).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("an empty declaration claims nothing", () => {
|
|
57
|
+
expect(claimedFieldPaths(claimedFieldsOfProps({}, { entityType: "T" }))).toEqual([]);
|
|
58
|
+
expect(claimedFieldPaths(NO_CLAIMED_FIELDS)).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("agrees with the tree form on an already-normalized tree", () => {
|
|
62
|
+
const props = { spec: { replicas: 2 } };
|
|
63
|
+
expect(claimedFieldPaths(claimedFieldsOfProps(props, { entityType: "T" }))).toEqual(
|
|
64
|
+
claimedFieldPaths(claimedFieldsOfTree(props, { entityType: "T" })),
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("isClaimed", () => {
|
|
70
|
+
const claimed = claimedFieldsFromPaths(["Tags[0].Value", "spec.replicas"]);
|
|
71
|
+
|
|
72
|
+
test("matches exactly", () => {
|
|
73
|
+
expect(isClaimed(claimed, "spec.replicas")).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("does not match by index-erased pattern", () => {
|
|
77
|
+
// A pattern match would answer "declared" for a path with no declared value
|
|
78
|
+
// to compare against, which is neither of the two declared classifications.
|
|
79
|
+
expect(isClaimed(claimed, "Tags[3].Value")).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("does not match an ancestor's claim", () => {
|
|
83
|
+
// Declaring `spec.replicas` says nothing about `spec.strategy.type`.
|
|
84
|
+
expect(isClaimed(claimed, "spec.strategy.type")).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("an absent claim claims nothing", () => {
|
|
88
|
+
expect(isClaimed(undefined, "spec.replicas")).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("classifyLiveField", () => {
|
|
93
|
+
const claimed = claimedFieldsFromPaths(["spec.replicas"]);
|
|
94
|
+
|
|
95
|
+
test("declared and equal", () => {
|
|
96
|
+
expect(
|
|
97
|
+
classifyLiveField({ claimed, path: "spec.replicas", declaredValue: 2, liveValue: 2 }),
|
|
98
|
+
).toBe("declared-equal");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("declared and different", () => {
|
|
102
|
+
expect(
|
|
103
|
+
classifyLiveField({ claimed, path: "spec.replicas", declaredValue: 2, liveValue: 5 }),
|
|
104
|
+
).toBe("declared-changed");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("undeclared", () => {
|
|
108
|
+
expect(
|
|
109
|
+
classifyLiveField({ claimed, path: "metadata.labels.team", declaredValue: undefined, liveValue: "platform" }),
|
|
110
|
+
).toBe("undeclared");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("structural equality, not identity", () => {
|
|
114
|
+
const shaped = claimedFieldsFromPaths(["spec.selector"]);
|
|
115
|
+
expect(
|
|
116
|
+
classifyLiveField({
|
|
117
|
+
claimed: shaped,
|
|
118
|
+
path: "spec.selector",
|
|
119
|
+
declaredValue: { app: "web" },
|
|
120
|
+
liveValue: { app: "web" },
|
|
121
|
+
}),
|
|
122
|
+
).toBe("declared-equal");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("heldBy", () => {
|
|
127
|
+
test("the substrate's manager answers where it has one", () => {
|
|
128
|
+
expect(heldBy({ "spec.replicas": "hpa-controller" }, "spec.replicas")).toEqual({
|
|
129
|
+
holder: "hpa-controller",
|
|
130
|
+
source: "field-manager",
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("the claim answers where the substrate records nothing", () => {
|
|
135
|
+
expect(heldBy(undefined, "spec.replicas")).toEqual({ source: "claimed-fields" });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("the claim answers for a path the ownership metadata does not cover", () => {
|
|
139
|
+
// Kubernetes' own case: a keyed list segment has no `managedFields` entry
|
|
140
|
+
// under that path, so the declaration is the only witness left.
|
|
141
|
+
expect(heldBy({ "spec.replicas": "hpa-controller" }, "spec.template.spec.containers[#sidecar].image")).toEqual({
|
|
142
|
+
source: "claimed-fields",
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|