@intentius/chant 0.80.0 → 0.82.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/discover.d.ts +8 -16
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts +5 -0
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +13 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/terraform-state.d.ts +4 -5
- package/dist/audit/terraform-state.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +25 -9
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +8 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/import-agents.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/update.d.ts +6 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/vendor.d.ts +5 -0
- package/dist/cli/commands/vendor.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/init.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/promote.d.ts +7 -4
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/run-generate.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts +4 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/vendor.d.ts +8 -3
- package/dist/cli/handlers/vendor.d.ts.map +1 -1
- package/dist/cli/main.d.ts +12 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts +12 -7
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +32 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts +10 -0
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +3 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/driver.d.ts +16 -2
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/gate-plan.d.ts +40 -0
- package/dist/components/gate-plan.d.ts.map +1 -0
- package/dist/components/promote.d.ts +60 -7
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/components/sandbox/run.d.ts.map +1 -1
- package/dist/components/verbs/build-archive.d.ts +22 -11
- package/dist/components/verbs/build-archive.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/config-static.d.ts +59 -0
- package/dist/config-static.d.ts.map +1 -0
- package/dist/config.d.ts +32 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/content-digest.d.ts +17 -0
- package/dist/content-digest.d.ts.map +1 -0
- package/dist/discovery/files.d.ts +10 -5
- package/dist/discovery/files.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +20 -4
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.d.ts +20 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/discovery/sandbox/fork.d.ts +20 -0
- package/dist/discovery/sandbox/fork.d.ts.map +1 -1
- package/dist/discovery/walk.d.ts +119 -0
- package/dist/discovery/walk.d.ts.map +1 -0
- package/dist/graph-ir.d.ts +4 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/join-key.d.ts +31 -0
- package/dist/join-key.d.ts.map +1 -0
- package/dist/lexicon-module.d.ts +51 -6
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon.d.ts +58 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
- package/dist/lifecycle/digest.d.ts +5 -1
- package/dist/lifecycle/digest.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +20 -5
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +64 -0
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +8 -1
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/legacy-digest.d.ts +41 -8
- package/dist/lifecycle/legacy-digest.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts +67 -0
- package/dist/lifecycle/member-ledger.d.ts.map +1 -0
- package/dist/lifecycle/plan-digest.d.ts +4 -3
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts +18 -2
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts +90 -5
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +32 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +75 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/gate-name.d.ts +7 -0
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-summary.d.ts +16 -0
- package/dist/op/gate-summary.d.ts.map +1 -1
- package/dist/op/gate.d.ts +16 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/project-root.d.ts +26 -0
- package/dist/project-root.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
- package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
- package/dist/workspace/checks/generated.d.ts +156 -0
- package/dist/workspace/checks/generated.d.ts.map +1 -0
- package/dist/workspace/checks/ledgers.d.ts +78 -0
- package/dist/workspace/checks/ledgers.d.ts.map +1 -0
- package/dist/workspace/checks/links.d.ts +25 -0
- package/dist/workspace/checks/links.d.ts.map +1 -0
- package/dist/workspace/checks/pipelines.d.ts +76 -0
- package/dist/workspace/checks/pipelines.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +161 -0
- package/dist/workspace/checks.d.ts.map +1 -0
- package/dist/workspace/compose-graph.d.ts +115 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -0
- package/dist/workspace/compose-reports.d.ts +68 -0
- package/dist/workspace/compose-reports.d.ts.map +1 -0
- package/dist/workspace/declaration.d.ts +220 -0
- package/dist/workspace/declaration.d.ts.map +1 -0
- package/dist/workspace/declaration.schema.json +434 -0
- package/dist/workspace/generated-files.d.ts +65 -0
- package/dist/workspace/generated-files.d.ts.map +1 -0
- package/dist/workspace/glob.d.ts +13 -0
- package/dist/workspace/glob.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts +75 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +96 -0
- package/dist/workspace/init.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +38 -0
- package/dist/workspace/jsonc.d.ts.map +1 -0
- package/dist/workspace/kinds.d.ts +192 -0
- package/dist/workspace/kinds.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +84 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -0
- package/dist/workspace/lineage-cli.d.ts +35 -0
- package/dist/workspace/lineage-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +144 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -0
- package/dist/workspace/lineage-lock.d.ts +271 -0
- package/dist/workspace/lineage-lock.d.ts.map +1 -0
- package/dist/workspace/lineage-merge.d.ts +14 -0
- package/dist/workspace/lineage-merge.d.ts.map +1 -0
- package/dist/workspace/lineage-migrations.d.ts +178 -0
- package/dist/workspace/lineage-migrations.d.ts.map +1 -0
- package/dist/workspace/lineage-update.d.ts +59 -0
- package/dist/workspace/lineage-update.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade.d.ts +152 -0
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
- package/dist/workspace/lineage-vendor.d.ts +63 -0
- package/dist/workspace/lineage-vendor.d.ts.map +1 -0
- package/dist/workspace/lineage-version.d.ts +32 -0
- package/dist/workspace/lineage-version.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +150 -0
- package/dist/workspace/links.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +99 -0
- package/dist/workspace/ls.d.ts.map +1 -0
- package/dist/workspace/member-commands.d.ts +139 -0
- package/dist/workspace/member-commands.d.ts.map +1 -0
- package/dist/workspace/member-handles.d.ts +54 -0
- package/dist/workspace/member-handles.d.ts.map +1 -0
- package/dist/workspace/member-pipeline.d.ts +143 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -0
- package/dist/workspace/member-run.d.ts +67 -0
- package/dist/workspace/member-run.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +65 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +28 -4
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/root-refusal.d.ts +53 -0
- package/dist/workspace/root-refusal.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +150 -0
- package/dist/workspace/status.d.ts.map +1 -0
- package/dist/workspace/template-manifest.d.ts +90 -0
- package/dist/workspace/template-manifest.d.ts.map +1 -0
- package/dist/workspace/tree.d.ts +36 -0
- package/dist/workspace/tree.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts +67 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +111 -0
- package/dist/workspace/trust/policy.d.ts.map +1 -0
- package/dist/workspace/trust/provenance.d.ts +67 -0
- package/dist/workspace/trust/provenance.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +43 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
- package/dist/workspace/trust/test-repo.d.ts +42 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -0
- package/dist/workspace/trust/verify-cli.d.ts +9 -0
- package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
- package/dist/workspace/trust/verify.d.ts +66 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -0
- package/dist/workspace/which-chant.d.ts +58 -0
- package/dist/workspace/which-chant.d.ts.map +1 -0
- package/dist/workspace/workspace-kinds.schema.json +133 -0
- package/package.json +2 -1
- package/src/audit/discover.ts +66 -78
- package/src/audit/report-html.ts +3 -1
- package/src/audit/report-model.ts +14 -1
- package/src/audit/terraform-state.test.ts +4 -9
- package/src/audit/terraform-state.ts +4 -5
- package/src/cli/commands/audit-truncation.test.ts +167 -0
- package/src/cli/commands/audit.ts +89 -60
- package/src/cli/commands/check-lexicon.ts +24 -0
- package/src/cli/commands/doctor.test.ts +30 -0
- package/src/cli/commands/doctor.ts +18 -0
- package/src/cli/commands/import-agents.ts +6 -2
- package/src/cli/commands/init.ts +22 -3
- package/src/cli/commands/lint.test.ts +17 -23
- package/src/cli/commands/lint.ts +24 -84
- package/src/cli/commands/update.ts +13 -4
- package/src/cli/commands/vendor.ts +11 -6
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
- package/src/cli/handlers/build-member.test.ts +127 -0
- package/src/cli/handlers/build.ts +35 -1
- package/src/cli/handlers/components.test.ts +127 -0
- package/src/cli/handlers/components.ts +70 -8
- package/src/cli/handlers/dev.ts +5 -3
- package/src/cli/handlers/fan-out.ts +9 -2
- package/src/cli/handlers/init.ts +58 -1
- package/src/cli/handlers/operator.test.ts +68 -0
- package/src/cli/handlers/operator.ts +37 -9
- package/src/cli/handlers/promote.test.ts +118 -2
- package/src/cli/handlers/promote.ts +82 -16
- package/src/cli/handlers/run-generate-member.test.ts +83 -0
- package/src/cli/handlers/run-generate.test.ts +6 -8
- package/src/cli/handlers/run-generate.ts +36 -1
- package/src/cli/handlers/run.test.ts +47 -0
- package/src/cli/handlers/run.ts +28 -5
- package/src/cli/handlers/vendor.ts +147 -43
- package/src/cli/main.test.ts +105 -1
- package/src/cli/main.ts +232 -17
- package/src/cli/plugins.ts +17 -17
- package/src/cli/registry.ts +32 -0
- package/src/cli/static-config-read.test.ts +208 -0
- package/src/codegen/fetch.test.ts +63 -1
- package/src/codegen/fetch.ts +30 -1
- package/src/components/cli-support.ts +6 -1
- package/src/components/discover.test.ts +27 -57
- package/src/components/discover.ts +13 -68
- package/src/components/driver.test.ts +78 -1
- package/src/components/driver.ts +43 -7
- package/src/components/gate-plan.ts +53 -0
- package/src/components/promote.test.ts +276 -2
- package/src/components/promote.ts +119 -15
- package/src/components/sandbox/driver.ts +8 -8
- package/src/components/sandbox/run.ts +4 -1
- package/src/components/verbs/build-archive.ts +27 -21
- package/src/components/verbs/run-agent.ts +3 -5
- package/src/config-static.test.ts +136 -0
- package/src/config-static.ts +232 -0
- package/src/config.test.ts +2 -2
- package/src/config.ts +48 -15
- package/src/content-digest.test.ts +120 -0
- package/src/content-digest.ts +21 -0
- package/src/discovery/files.test.ts +10 -10
- package/src/discovery/files.ts +25 -81
- package/src/discovery/fold-import.ts +49 -5
- package/src/discovery/index.ts +1 -0
- package/src/discovery/sandbox/driver.ts +73 -35
- package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
- package/src/discovery/sandbox/fork.ts +83 -43
- package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
- package/src/discovery/walk.test.ts +263 -0
- package/src/discovery/walk.ts +334 -0
- package/src/effect-receipt.ts +2 -2
- package/src/graph-ir.ts +4 -1
- package/src/join-key.test.ts +25 -0
- package/src/join-key.ts +39 -0
- package/src/lexicon-module.ts +108 -10
- package/src/lexicon.ts +79 -1
- package/src/lifecycle/build-ledger-store.ts +11 -10
- package/src/lifecycle/digest.ts +9 -5
- package/src/lifecycle/gate-ledger.test.ts +20 -0
- package/src/lifecycle/gate-ledger.ts +28 -6
- package/src/lifecycle/git.test.ts +13 -1
- package/src/lifecycle/git.ts +221 -176
- package/src/lifecycle/lease.ts +25 -10
- package/src/lifecycle/legacy-digest.test.ts +115 -23
- package/src/lifecycle/legacy-digest.ts +60 -19
- package/src/lifecycle/member-ledger.test.ts +319 -0
- package/src/lifecycle/member-ledger.ts +145 -0
- package/src/lifecycle/plan-digest.ts +7 -7
- package/src/lifecycle/release-ledger.test.ts +20 -0
- package/src/lifecycle/release-ledger.ts +23 -7
- package/src/lifecycle/status.test.ts +151 -1
- package/src/lifecycle/status.ts +228 -7
- package/src/op/activities/activity-contracts.ts +37 -0
- package/src/op/activities/index.ts +10 -0
- package/src/op/activities/propose-upgrade.ts +206 -0
- package/src/op/discover.test.ts +14 -4
- package/src/op/discover.ts +19 -27
- package/src/op/gate-name.ts +8 -0
- package/src/op/gate-summary.test.ts +14 -0
- package/src/op/gate-summary.ts +25 -2
- package/src/op/gate.ts +36 -7
- package/src/op/generate-pipeline.test.ts +14 -5
- package/src/project-root.test.ts +66 -1
- package/src/project-root.ts +48 -0
- package/src/workspace/__fixtures__/contract-repo.ts +100 -0
- package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
- package/src/workspace/behold-kinds.test.ts +158 -0
- package/src/workspace/check-contract.test.ts +127 -0
- package/src/workspace/check.schema.json +347 -0
- package/src/workspace/checks/generated.test.ts +287 -0
- package/src/workspace/checks/generated.ts +475 -0
- package/src/workspace/checks/ledgers.test.ts +139 -0
- package/src/workspace/checks/ledgers.ts +246 -0
- package/src/workspace/checks/links.ts +183 -0
- package/src/workspace/checks/pipelines.test.ts +133 -0
- package/src/workspace/checks/pipelines.ts +225 -0
- package/src/workspace/checks-repo.test.ts +71 -0
- package/src/workspace/checks.test.ts +450 -0
- package/src/workspace/checks.ts +461 -0
- package/src/workspace/compose-graph.test.ts +146 -0
- package/src/workspace/compose-graph.ts +228 -0
- package/src/workspace/compose-reports.test.ts +109 -0
- package/src/workspace/compose-reports.ts +186 -0
- package/src/workspace/declaration.schema.json +434 -0
- package/src/workspace/declaration.test.ts +347 -0
- package/src/workspace/declaration.ts +623 -0
- package/src/workspace/generated-files.test.ts +114 -0
- package/src/workspace/generated-files.ts +94 -0
- package/src/workspace/glob.ts +49 -0
- package/src/workspace/graph-cli.ts +220 -0
- package/src/workspace/graph-contract.test.ts +204 -0
- package/src/workspace/graph.schema.json +662 -0
- package/src/workspace/init.test.ts +223 -0
- package/src/workspace/init.ts +585 -0
- package/src/workspace/jsonc.test.ts +65 -0
- package/src/workspace/jsonc.ts +231 -0
- package/src/workspace/kinds.test.ts +288 -0
- package/src/workspace/kinds.ts +506 -0
- package/src/workspace/lineage-check.ts +264 -0
- package/src/workspace/lineage-cli.ts +134 -0
- package/src/workspace/lineage-init-dir.test.ts +243 -0
- package/src/workspace/lineage-init.test.ts +231 -0
- package/src/workspace/lineage-init.ts +560 -0
- package/src/workspace/lineage-lock.test.ts +224 -0
- package/src/workspace/lineage-lock.ts +365 -0
- package/src/workspace/lineage-merge.ts +44 -0
- package/src/workspace/lineage-migrations.test.ts +178 -0
- package/src/workspace/lineage-migrations.ts +343 -0
- package/src/workspace/lineage-update.ts +160 -0
- package/src/workspace/lineage-upgrade-cli.ts +151 -0
- package/src/workspace/lineage-upgrade.test.ts +474 -0
- package/src/workspace/lineage-upgrade.ts +723 -0
- package/src/workspace/lineage-vendor.test.ts +126 -0
- package/src/workspace/lineage-vendor.ts +215 -0
- package/src/workspace/lineage-version.ts +127 -0
- package/src/workspace/links.test.ts +344 -0
- package/src/workspace/links.ts +331 -0
- package/src/workspace/ls-contract.test.ts +200 -0
- package/src/workspace/ls.schema.json +190 -0
- package/src/workspace/ls.ts +254 -0
- package/src/workspace/member-commands.e2e.test.ts +244 -0
- package/src/workspace/member-commands.test.ts +217 -0
- package/src/workspace/member-commands.ts +540 -0
- package/src/workspace/member-handles.test.ts +76 -0
- package/src/workspace/member-handles.ts +228 -0
- package/src/workspace/member-pipeline.test.ts +135 -0
- package/src/workspace/member-pipeline.ts +317 -0
- package/src/workspace/member-run.ts +146 -0
- package/src/workspace/read-contract.test.ts +129 -0
- package/src/workspace/reason-codes.test.ts +113 -0
- package/src/workspace/reason-codes.ts +81 -0
- package/src/workspace/records-cli.ts +86 -14
- package/src/workspace/records-contract.test.ts +5 -0
- package/src/workspace/records.schema.json +32 -2
- package/src/workspace/records.ts +3 -2
- package/src/workspace/root-refusal.ts +97 -0
- package/src/workspace/status-contract.test.ts +308 -0
- package/src/workspace/status.schema.json +210 -0
- package/src/workspace/status.ts +412 -0
- package/src/workspace/template-manifest.test.ts +79 -0
- package/src/workspace/template-manifest.ts +221 -0
- package/src/workspace/tree.ts +132 -0
- package/src/workspace/trust/attestor.ts +93 -0
- package/src/workspace/trust/policy.ts +306 -0
- package/src/workspace/trust/provenance.ts +181 -0
- package/src/workspace/trust/ssh-commit.ts +138 -0
- package/src/workspace/trust/test-repo.ts +138 -0
- package/src/workspace/trust/trust.test.ts +397 -0
- package/src/workspace/trust/verify-cli.ts +56 -0
- package/src/workspace/trust/verify.ts +194 -0
- package/src/workspace/which-chant.test.ts +117 -0
- package/src/workspace/which-chant.ts +151 -0
- package/src/workspace/workspace-kinds.schema.json +133 -0
- package/dist/components/unbound-gate-approval.d.ts +0 -24
- package/dist/components/unbound-gate-approval.d.ts.map +0 -1
- package/dist/discovery/convergence.d.ts +0 -97
- package/dist/discovery/convergence.d.ts.map +0 -1
- package/src/cli/commands/audit-walk-warnings.test.ts +0 -156
- package/src/components/unbound-gate-approval.test.ts +0 -82
- package/src/components/unbound-gate-approval.ts +0 -51
- package/src/discovery/convergence.test.ts +0 -231
- package/src/discovery/convergence.ts +0 -399
- package/src/op/discover-convergence.test.ts +0 -70
|
@@ -129,6 +129,12 @@ export interface AuditReportJson {
|
|
|
129
129
|
findings: SerializedFinding[];
|
|
130
130
|
/** Candidate files that looked like they wanted a lexicon that is not installed. */
|
|
131
131
|
unclaimed?: UnclaimedFile[];
|
|
132
|
+
/**
|
|
133
|
+
* Present when the local walk stopped at its file limit with files left
|
|
134
|
+
* unread (#2528): the limit, and the flag that raises it. Absent for a whole
|
|
135
|
+
* scan and for a repository URL.
|
|
136
|
+
*/
|
|
137
|
+
truncated?: { limit: number; flag: string };
|
|
132
138
|
}
|
|
133
139
|
|
|
134
140
|
export function metaFor(id: string, catalog: Record<string, RuleMeta> = RULE_CATALOG): RuleMeta {
|
|
@@ -298,7 +304,13 @@ export function buildReportModel(findings: AuditFinding[], opts: BuildModelOptio
|
|
|
298
304
|
/** Build the versioned, machine-readable JSON report (stable contract). */
|
|
299
305
|
export function buildReportJson(
|
|
300
306
|
findings: AuditFinding[],
|
|
301
|
-
opts: {
|
|
307
|
+
opts: {
|
|
308
|
+
snapshot?: AuditSnapshot;
|
|
309
|
+
toolVersion?: string;
|
|
310
|
+
catalog?: Record<string, RuleMeta>;
|
|
311
|
+
unclaimed?: UnclaimedFile[];
|
|
312
|
+
truncated?: { limit: number; flag: string };
|
|
313
|
+
} = {},
|
|
302
314
|
): AuditReportJson {
|
|
303
315
|
const model = buildReportModel(findings, { catalog: opts.catalog });
|
|
304
316
|
const version = opts.toolVersion ?? opts.snapshot?.toolVersion ?? "0.0.0";
|
|
@@ -328,5 +340,6 @@ export function buildReportJson(
|
|
|
328
340
|
docUrl: ruleDocUrl(f.checkId),
|
|
329
341
|
})),
|
|
330
342
|
unclaimed: opts.unclaimed && opts.unclaimed.length > 0 ? opts.unclaimed : undefined,
|
|
343
|
+
truncated: opts.truncated,
|
|
331
344
|
};
|
|
332
345
|
}
|
|
@@ -196,22 +196,18 @@ describe("walkCandidates reports what the next release changes, and changes noth
|
|
|
196
196
|
rmSync(dir, { recursive: true, force: true });
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
-
test("a state file a nested .gitignore ignores is
|
|
199
|
+
test("a state file a nested .gitignore ignores is not a candidate (#2528)", () => {
|
|
200
200
|
const dir = tmpRepo();
|
|
201
201
|
mkdirSync(join(dir, "infra", ".terraform"), { recursive: true });
|
|
202
202
|
writeFileSync(join(dir, "infra", ".gitignore"), "*.tfstate\n.terraform/\n");
|
|
203
203
|
writeFileSync(join(dir, "infra", "terraform.tfstate"), "{}");
|
|
204
204
|
const walk = walkCandidates(dir);
|
|
205
|
-
expect(walk.files.map((f) => f.path)
|
|
206
|
-
expect(auditTerraformState(walk.files)).toHaveLength(
|
|
207
|
-
expect(walk.nestedGitignore).toEqual([
|
|
208
|
-
{ path: "infra/.terraform", gitignore: "infra/.gitignore" },
|
|
209
|
-
{ path: "infra/terraform.tfstate", gitignore: "infra/.gitignore" },
|
|
210
|
-
]);
|
|
205
|
+
expect(walk.files.map((f) => f.path)).toEqual([]);
|
|
206
|
+
expect(auditTerraformState(walk.files)).toHaveLength(0);
|
|
211
207
|
rmSync(dir, { recursive: true, force: true });
|
|
212
208
|
});
|
|
213
209
|
|
|
214
|
-
test("
|
|
210
|
+
test("only the .gitignore files between the path and the root apply, not a sibling's", () => {
|
|
215
211
|
const dir = tmpRepo();
|
|
216
212
|
mkdirSync(join(dir, "a"), { recursive: true });
|
|
217
213
|
mkdirSync(join(dir, "b"), { recursive: true });
|
|
@@ -223,7 +219,6 @@ describe("walkCandidates reports what the next release changes, and changes noth
|
|
|
223
219
|
writeFileSync(join(dir, "b", ".gitignore"), "*.tfstate\n");
|
|
224
220
|
const walk = walkCandidates(dir);
|
|
225
221
|
expect(walk.files.map((f) => f.path)).toEqual(["a/terraform.tfstate"]);
|
|
226
|
-
expect(walk.nestedGitignore).toEqual([]);
|
|
227
222
|
rmSync(dir, { recursive: true, force: true });
|
|
228
223
|
});
|
|
229
224
|
});
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
* walk that decides which paths reach here is `discover.ts`'s
|
|
23
23
|
* `collectCandidates`, which is also where a locally ignored path is dropped:
|
|
24
24
|
* see `isTerraformStatePath` there and `gitignoreCoversTerraformState` below.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* The root `.gitignore` and every nested one on the way to the path can drop
|
|
26
|
+
* it (`nestedGitignoreCovering`, #2528).
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import type { AuditFinding } from "./core";
|
|
@@ -92,9 +92,8 @@ export function gitignoreCoversTerraformState(gitignore: string, path: string):
|
|
|
92
92
|
* the same narrow matcher as the root one. `readGitignore` takes a directory
|
|
93
93
|
* relative to the scan root and returns its `.gitignore` body, if any.
|
|
94
94
|
*
|
|
95
|
-
* The walk
|
|
96
|
-
*
|
|
97
|
-
* once every `.gitignore` is read (#2528).
|
|
95
|
+
* The local walk drops a TF023 path this returns a directory for, as it drops
|
|
96
|
+
* one the root `.gitignore` covers (#2528).
|
|
98
97
|
*/
|
|
99
98
|
export function nestedGitignoreCovering(
|
|
100
99
|
path: string,
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { afterEach, beforeAll, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { auditCommand, printAuditResult, type AuditCommandOptions } from "./audit";
|
|
6
|
+
import { loadAuditPlugins } from "../../audit/discover";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* #2528, one of the level-0 changes on #2525's exception list (warned in
|
|
10
|
+
* v0.80.0). A truncated local walk is stated in the report, with its limit and
|
|
11
|
+
* the flag that raises it, and TF023 honours every `.gitignore` between a path
|
|
12
|
+
* and the scan root.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const NOW = "2026-09-23T00:00:00.000Z";
|
|
16
|
+
const dirs: string[] = [];
|
|
17
|
+
|
|
18
|
+
function tmpRepo(): string {
|
|
19
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-2528-"));
|
|
20
|
+
dirs.push(dir);
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true });
|
|
26
|
+
vi.restoreAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
let plugins: Awaited<ReturnType<typeof loadAuditPlugins>>;
|
|
30
|
+
|
|
31
|
+
// Loading every audit lexicon is the slow part; once, outside any one test's timeout.
|
|
32
|
+
beforeAll(async () => {
|
|
33
|
+
plugins = await loadAuditPlugins();
|
|
34
|
+
}, 120_000);
|
|
35
|
+
|
|
36
|
+
function audit(path: string, extra: Partial<AuditCommandOptions> = {}) {
|
|
37
|
+
return auditCommand({ path, format: "json", now: NOW, toolVersion: "0.0.0", plugins, ...extra });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Print a result the way the CLI does, and split what reached each stream. */
|
|
41
|
+
function printed(result: Awaited<ReturnType<typeof auditCommand>>): { stdout: string; stderr: string } {
|
|
42
|
+
const out: string[] = [];
|
|
43
|
+
const err: string[] = [];
|
|
44
|
+
vi.spyOn(console, "log").mockImplementation((s: string) => void out.push(s));
|
|
45
|
+
vi.spyOn(console, "error").mockImplementation((s: string) => void err.push(s));
|
|
46
|
+
printAuditResult(result);
|
|
47
|
+
vi.restoreAllMocks();
|
|
48
|
+
return { stdout: out.join("\n"), stderr: err.join("\n") };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("a truncated walk is reported (#2528)", () => {
|
|
52
|
+
function treeWithTail(): string {
|
|
53
|
+
const dir = tmpRepo();
|
|
54
|
+
mkdirSync(join(dir, "a"), { recursive: true });
|
|
55
|
+
for (let i = 0; i < 5; i++) writeFileSync(join(dir, "a", `${i}.md`), "x");
|
|
56
|
+
writeFileSync(join(dir, "a", "terraform.tfstate"), "{}");
|
|
57
|
+
// Past the limit: a state file TF023 would report if the walk reached it.
|
|
58
|
+
mkdirSync(join(dir, "z"), { recursive: true });
|
|
59
|
+
writeFileSync(join(dir, "z", "terraform.tfstate"), "{}");
|
|
60
|
+
return dir;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test("JSON carries truncated with the limit and the flag, and nothing goes to stderr", async () => {
|
|
64
|
+
const dir = treeWithTail();
|
|
65
|
+
const result = await audit(dir, { maxFiles: 6 });
|
|
66
|
+
expect(JSON.parse(result.output).truncated).toEqual({ limit: 6, flag: "--max-files" });
|
|
67
|
+
expect(result.warnings).toBeUndefined();
|
|
68
|
+
const { stdout, stderr } = printed(result);
|
|
69
|
+
expect(stdout).toBe(result.output);
|
|
70
|
+
expect(stderr).toBe("");
|
|
71
|
+
expect(result.findings.map((f) => f.file)).toEqual(["a/terraform.tfstate"]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("the text report leads with a note naming the limit and --max-files", async () => {
|
|
75
|
+
const dir = treeWithTail();
|
|
76
|
+
const result = await audit(dir, { maxFiles: 6, format: "stylish" });
|
|
77
|
+
const first = result.output.split("\n")[0];
|
|
78
|
+
expect(first).toBe(
|
|
79
|
+
`Note: The scan of ${dir} stopped at 6 files, so part of the tree was not audited. ` +
|
|
80
|
+
"The limit counts every file the walk reaches, not only the ones it audits. Raise it with --max-files <n>.",
|
|
81
|
+
);
|
|
82
|
+
expect(result.warnings).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("markdown and HTML carry the note, and HTML's embedded JSON carries the field", async () => {
|
|
86
|
+
const dir = treeWithTail();
|
|
87
|
+
const md = await audit(dir, { maxFiles: 6, format: "markdown" });
|
|
88
|
+
expect(md.output).toContain("stopped at 6 files");
|
|
89
|
+
const html = await audit(dir, { maxFiles: 6, format: "html" });
|
|
90
|
+
expect(html.output).toContain("stopped at 6 files");
|
|
91
|
+
expect(html.output).toContain('"truncated":{"limit":6,"flag":"--max-files"}');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("SARIF has no slot for it, so the truncation goes to stderr there", async () => {
|
|
95
|
+
const dir = treeWithTail();
|
|
96
|
+
const result = await audit(dir, { maxFiles: 6, format: "sarif" });
|
|
97
|
+
const { stdout, stderr } = printed(result);
|
|
98
|
+
expect(stdout).toBe(result.output);
|
|
99
|
+
expect(stderr).toContain(`The scan of ${dir} stopped at 6 files`);
|
|
100
|
+
expect(stderr).toContain("--max-files <n>");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("a whole scan reports nothing: no field, no note", async () => {
|
|
104
|
+
const dir = treeWithTail();
|
|
105
|
+
const raised = await audit(dir, { maxFiles: 7 });
|
|
106
|
+
expect(JSON.parse(raised.output)).not.toHaveProperty("truncated");
|
|
107
|
+
expect(raised.warnings).toBeUndefined();
|
|
108
|
+
expect(raised.findings.map((f) => f.file).sort()).toEqual(["a/terraform.tfstate", "z/terraform.tfstate"]);
|
|
109
|
+
const text = await audit(dir, { format: "stylish" });
|
|
110
|
+
expect(text.output).not.toContain("stopped at");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("the no-lexicons report states it too", async () => {
|
|
114
|
+
const dir = treeWithTail();
|
|
115
|
+
const json = await audit(dir, { maxFiles: 6, plugins: [] });
|
|
116
|
+
expect(JSON.parse(json.output).truncated).toEqual({ limit: 6, flag: "--max-files" });
|
|
117
|
+
const text = await audit(dir, { maxFiles: 6, plugins: [], format: "stylish" });
|
|
118
|
+
expect(text.output).toContain("stopped at 6 files");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("an audit with nothing auditable still says the scan was cut short", async () => {
|
|
122
|
+
const dir = tmpRepo();
|
|
123
|
+
for (let i = 0; i < 4; i++) writeFileSync(join(dir, `${i}.md`), "x");
|
|
124
|
+
const result = await audit(dir, { maxFiles: 2, format: "stylish" });
|
|
125
|
+
expect(result.output).toContain("No auditable files found");
|
|
126
|
+
expect(result.output).toContain("stopped at 2 files");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("TF023 honours every .gitignore between a path and the scan root (#2528)", () => {
|
|
131
|
+
function nestedTree(nested: string | undefined): string {
|
|
132
|
+
const dir = tmpRepo();
|
|
133
|
+
mkdirSync(join(dir, "infra", "prod"), { recursive: true });
|
|
134
|
+
writeFileSync(join(dir, ".gitignore"), "node_modules/\n");
|
|
135
|
+
writeFileSync(join(dir, "infra", "main.tf"), 'resource "null_resource" "a" {}\n');
|
|
136
|
+
writeFileSync(join(dir, "infra", "terraform.tfstate"), "{}");
|
|
137
|
+
writeFileSync(join(dir, "infra", "prod", "terraform.tfstate"), "{}");
|
|
138
|
+
if (nested !== undefined) writeFileSync(join(dir, "infra", ".gitignore"), nested);
|
|
139
|
+
return dir;
|
|
140
|
+
}
|
|
141
|
+
const tf023 = (r: Awaited<ReturnType<typeof audit>>) =>
|
|
142
|
+
r.findings.filter((f) => f.checkId === "TF023").map((f) => f.file).sort();
|
|
143
|
+
|
|
144
|
+
test("a nested .gitignore drops the paths it covers, at any depth below it", async () => {
|
|
145
|
+
const result = await audit(nestedTree("*.tfstate\n"));
|
|
146
|
+
expect(tf023(result)).toEqual([]);
|
|
147
|
+
expect(result.warnings).toBeUndefined();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("a nested .gitignore that does not cover the file changes nothing", async () => {
|
|
151
|
+
expect(tf023(await audit(nestedTree("*.log\n")))).toEqual(["infra/prod/terraform.tfstate", "infra/terraform.tfstate"]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("a .gitignore deeper than the file does not apply to it", async () => {
|
|
155
|
+
const dir = nestedTree(undefined);
|
|
156
|
+
writeFileSync(join(dir, "infra", "prod", ".gitignore"), "*.tfstate\n");
|
|
157
|
+
expect(tf023(await audit(dir))).toEqual(["infra/terraform.tfstate"]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("auditing from the root and from the subdirectory agree", async () => {
|
|
161
|
+
const dir = nestedTree(undefined);
|
|
162
|
+
writeFileSync(join(dir, "infra", "prod", ".gitignore"), "*.tfstate\n");
|
|
163
|
+
const fromRoot = tf023(await audit(dir));
|
|
164
|
+
const fromInfra = tf023(await audit(join(dir, "infra"))).map((p) => `infra/${p}`);
|
|
165
|
+
expect(fromRoot).toEqual(fromInfra);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { existsSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
9
|
-
import { join, resolve } from "path";
|
|
9
|
+
import { dirname, join, resolve } from "path";
|
|
10
10
|
import { auditFiles, type AuditInput, type AuditFinding, type ChecksProvider, type SuppressionStats } from "../../audit/core";
|
|
11
11
|
import { AUDIT_LEXICONS, classifyFiles, loadAuditPlugins, unclaimedFiles, walkCandidates, type CandidateWalk, type DetectPlugin, type RepoFile, type UnclaimedFile } from "../../audit/discover";
|
|
12
12
|
import { RULE_CATALOG, resolveAuditCatalog, type RuleMeta } from "../../audit/catalog";
|
|
13
13
|
import { scanForSecrets, parseSecretsConfig, type SecretsScanOptions } from "../../audit/secrets";
|
|
14
14
|
import { auditWranglerConfigs } from "../../audit/wrangler";
|
|
15
15
|
import { auditNginxConfigs } from "../../audit/nginx";
|
|
16
|
-
import { auditTerraformState
|
|
17
|
-
import {
|
|
16
|
+
import { auditTerraformState } from "../../audit/terraform-state";
|
|
17
|
+
import { workspaceMemberDirs } from "../../discovery/walk";
|
|
18
18
|
import { renderMarkdown } from "../../audit/report";
|
|
19
19
|
import { renderHtml, type ReportTheme } from "../../audit/report-html";
|
|
20
20
|
import { buildReportJson, REPORT_SCHEMA_VERSION, type AuditSnapshot } from "../../audit/report-model";
|
|
@@ -23,7 +23,8 @@ import { extractUnpinnedActions, extractUnpinnedImages } from "../../audit/proof
|
|
|
23
23
|
import type { ProveOptions } from "../../audit/proof";
|
|
24
24
|
import type { Severity } from "../../lint/rule";
|
|
25
25
|
import { formatWarning } from "../format";
|
|
26
|
-
import {
|
|
26
|
+
import { lexiconPackagesToInstall, lexiconSourceLabel, pathLexiconMap, recordedLexiconModules } from "../../lexicon-module";
|
|
27
|
+
import { readLexiconDeclarationsStatically, unknownPathLexiconsNotice } from "../../config-static";
|
|
27
28
|
|
|
28
29
|
export type AuditFormat = "stylish" | "json" | "sarif" | "markdown" | "html";
|
|
29
30
|
export type AuditTier = "merge-worthy" | "all";
|
|
@@ -116,7 +117,7 @@ export interface AuditCommandResult {
|
|
|
116
117
|
* summary line.
|
|
117
118
|
*/
|
|
118
119
|
suppressedCount?: number;
|
|
119
|
-
/** Run diagnostics for stderr (#2528). */
|
|
120
|
+
/** Run diagnostics for stderr: a truncated scan in a format that cannot carry it (#2528). */
|
|
120
121
|
warnings?: AuditWarning[];
|
|
121
122
|
}
|
|
122
123
|
|
|
@@ -129,16 +130,34 @@ export const NO_LEXICONS_EXIT_CODE = 2;
|
|
|
129
130
|
* `chant.config.ts` names by path (#2520), which has no package to install
|
|
130
131
|
* (chant#2578).
|
|
131
132
|
*/
|
|
132
|
-
function missingLexiconRemedy(names: readonly string[]): string {
|
|
133
|
+
function missingLexiconRemedy(names: readonly string[], paths: ReadonlyMap<string, string>): string {
|
|
133
134
|
const parts: string[] = [];
|
|
134
|
-
const pkgs = lexiconPackagesToInstall(names);
|
|
135
|
+
const pkgs = lexiconPackagesToInstall(names, paths);
|
|
135
136
|
if (pkgs.length > 0) parts.push(`npm i ${pkgs.join(" ")}`);
|
|
136
137
|
for (const name of names) {
|
|
137
|
-
if (
|
|
138
|
+
if (paths.has(name)) parts.push(`${name} is declared by path: ${lexiconSourceLabel(name, process.cwd(), paths)}`);
|
|
138
139
|
}
|
|
139
140
|
return parts.join("; ");
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
/**
|
|
144
|
+
* chant#2589 — the lexicons a local target's chant.config declares by path,
|
|
145
|
+
* with any recorded earlier in this process. The config is read statically
|
|
146
|
+
* (../../config-static.ts) and never evaluated, and the paths are not
|
|
147
|
+
* recorded for the loaders: audit names them in its messages, but never
|
|
148
|
+
* imports a project's lexicon module. `notice` is set when the config's
|
|
149
|
+
* `lexicons` could not be read that way.
|
|
150
|
+
*/
|
|
151
|
+
export function auditPathLexicons(target: string, isUrl: boolean): { paths: Map<string, string>; notice?: string } {
|
|
152
|
+
const paths = recordedLexiconModules();
|
|
153
|
+
if (isUrl) return { paths };
|
|
154
|
+
const read = readLexiconDeclarationsStatically(resolve(target));
|
|
155
|
+
if (read.status === "read") {
|
|
156
|
+
for (const [name, path] of pathLexiconMap(read.entries, dirname(read.configPath))) paths.set(name, path);
|
|
157
|
+
}
|
|
158
|
+
return { paths, notice: unknownPathLexiconsNotice(read) };
|
|
159
|
+
}
|
|
160
|
+
|
|
142
161
|
/** Missing audit lexicons the unclaimed files pointed at, in first-seen order. */
|
|
143
162
|
function wantedLexicons(unclaimed: UnclaimedFile[]): string[] {
|
|
144
163
|
return [...new Set(unclaimed.map((u) => u.lexicon))];
|
|
@@ -149,26 +168,31 @@ function wantedLexicons(unclaimed: UnclaimedFile[]): string[] {
|
|
|
149
168
|
* lexicons it needs. Every wanted lexicon is a `-p` package so npx puts all of
|
|
150
169
|
* them on the same resolution path.
|
|
151
170
|
*/
|
|
152
|
-
export function installLine(
|
|
171
|
+
export function installLine(
|
|
172
|
+
lexicons: string[],
|
|
173
|
+
target: string,
|
|
174
|
+
paths: ReadonlyMap<string, string> = recordedLexiconModules(),
|
|
175
|
+
): string {
|
|
153
176
|
// chant#2578 — a lexicon declared by path has no package for npx to fetch.
|
|
154
|
-
const pkgs = ["@intentius/chant", ...lexiconPackagesToInstall(lexicons)];
|
|
177
|
+
const pkgs = ["@intentius/chant", ...lexiconPackagesToInstall(lexicons, paths)];
|
|
155
178
|
return `npx ${pkgs.map((p) => `-p ${p}`).join(" ")} chant audit ${target}`;
|
|
156
179
|
}
|
|
157
180
|
|
|
158
181
|
/** One-line coverage hint for the partial case: some lexicons loaded, others wanted by files on disk. */
|
|
159
|
-
function missingLexiconHint(unclaimed: UnclaimedFile[]): string | undefined {
|
|
182
|
+
function missingLexiconHint(unclaimed: UnclaimedFile[], paths: ReadonlyMap<string, string>): string | undefined {
|
|
160
183
|
const wanted = wantedLexicons(unclaimed);
|
|
161
184
|
if (wanted.length === 0) return undefined;
|
|
162
185
|
const n = unclaimed.length;
|
|
163
186
|
return `${n} file${n === 1 ? " looks" : "s look"} like ${wanted.join("/")} but ${wanted.length === 1 ? "that lexicon is" : "those lexicons are"} not installed, so ${n === 1 ? "it was" : "they were"} skipped` +
|
|
164
|
-
` (${missingLexiconRemedy(wanted)}).`;
|
|
187
|
+
` (${missingLexiconRemedy(wanted, paths)}).`;
|
|
165
188
|
}
|
|
166
189
|
|
|
167
190
|
/** Human-readable diagnostic for the zero-lexicon case. */
|
|
168
|
-
function renderNoLexicons(target: string, unclaimed: UnclaimedFile[]): string {
|
|
191
|
+
function renderNoLexicons(target: string, unclaimed: UnclaimedFile[], paths: ReadonlyMap<string, string>, truncated?: AuditTruncation): string {
|
|
169
192
|
const lines: string[] = [];
|
|
170
193
|
lines.push(`chant audit had nothing to look with: no audit lexicon is installed, so nothing under ${target} was inspected.`);
|
|
171
194
|
lines.push("This is not a clean result. Detection and checks live in the lexicon packages.");
|
|
195
|
+
if (truncated) lines.push("", `Note: ${truncationNote(truncated, target)}`);
|
|
172
196
|
const wanted = wantedLexicons(unclaimed);
|
|
173
197
|
if (unclaimed.length > 0) {
|
|
174
198
|
lines.push("", "Files that wanted a lexicon:");
|
|
@@ -179,12 +203,17 @@ function renderNoLexicons(target: string, unclaimed: UnclaimedFile[]): string {
|
|
|
179
203
|
lines.push("", "No file under the target looked like CI, Kubernetes, Helm, Docker, CloudFormation, ARM, Config Connector, fountain, or Terraform either.");
|
|
180
204
|
}
|
|
181
205
|
lines.push("", "Run it with the lexicons those files need:");
|
|
182
|
-
|
|
206
|
+
const lexicons = wanted.length > 0 ? wanted : [...AUDIT_LEXICONS];
|
|
207
|
+
lines.push(` ${installLine(lexicons, target, paths)}`);
|
|
208
|
+
// chant#2589 — a lexicon declared by path is left out of the line above.
|
|
209
|
+
for (const name of lexicons) {
|
|
210
|
+
if (paths.has(name)) lines.push(` (${name} is declared by path: ${lexiconSourceLabel(name, process.cwd(), paths)})`);
|
|
211
|
+
}
|
|
183
212
|
return lines.join("\n");
|
|
184
213
|
}
|
|
185
214
|
|
|
186
215
|
/** Machine-readable form of the zero-lexicon diagnostic (`status: "no-lexicons"`). */
|
|
187
|
-
function renderNoLexiconsJson(target: string, unclaimed: UnclaimedFile[]): string {
|
|
216
|
+
function renderNoLexiconsJson(target: string, unclaimed: UnclaimedFile[], paths: ReadonlyMap<string, string>, truncated?: AuditTruncation): string {
|
|
188
217
|
const wanted = wantedLexicons(unclaimed);
|
|
189
218
|
return JSON.stringify(
|
|
190
219
|
{
|
|
@@ -196,7 +225,8 @@ function renderNoLexiconsJson(target: string, unclaimed: UnclaimedFile[]): strin
|
|
|
196
225
|
findings: [],
|
|
197
226
|
unclaimed,
|
|
198
227
|
missingLexicons: wanted,
|
|
199
|
-
install: installLine(wanted.length > 0 ? wanted : [...AUDIT_LEXICONS], target),
|
|
228
|
+
install: installLine(wanted.length > 0 ? wanted : [...AUDIT_LEXICONS], target, paths),
|
|
229
|
+
truncated,
|
|
200
230
|
},
|
|
201
231
|
null,
|
|
202
232
|
2,
|
|
@@ -406,32 +436,26 @@ async function buildSnapshot(options: AuditCommandOptions, files: string[], isUr
|
|
|
406
436
|
};
|
|
407
437
|
}
|
|
408
438
|
|
|
409
|
-
/**
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
message: `TF023 reports this path because chant audit reads only the root .gitignore, but ${change.gitignore} ignores it.`,
|
|
430
|
-
hint:
|
|
431
|
-
"From the next release chant audit reads every .gitignore between a file and the scan root, and TF023 will no longer report this path.",
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
return warnings;
|
|
439
|
+
/** The flag that raises the local walk's file limit, named wherever a truncated scan is reported. */
|
|
440
|
+
export const MAX_FILES_FLAG = "--max-files";
|
|
441
|
+
|
|
442
|
+
/** How a report states that the local walk stopped at its limit (#2528). JSON carries it as `truncated`. */
|
|
443
|
+
export interface AuditTruncation {
|
|
444
|
+
limit: number;
|
|
445
|
+
flag: typeof MAX_FILES_FLAG;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** The truncation a walk reports, or undefined for a whole scan. */
|
|
449
|
+
export function walkTruncation(walk: CandidateWalk): AuditTruncation | undefined {
|
|
450
|
+
return walk.truncated ? { limit: walk.maxFiles, flag: MAX_FILES_FLAG } : undefined;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** The text-report note for a truncated scan. */
|
|
454
|
+
export function truncationNote(t: AuditTruncation, target: string): string {
|
|
455
|
+
return (
|
|
456
|
+
`The scan of ${target} stopped at ${t.limit} files, so part of the tree was not audited. ` +
|
|
457
|
+
`The limit counts every file the walk reaches, not only the ones it audits. Raise it with ${t.flag} <n>.`
|
|
458
|
+
);
|
|
435
459
|
}
|
|
436
460
|
|
|
437
461
|
/** Run the audit and produce a rendered result. */
|
|
@@ -441,6 +465,8 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
|
|
|
441
465
|
const failOn = options.failOn ?? "none";
|
|
442
466
|
|
|
443
467
|
const isUrl = /^https?:\/\//.test(options.path);
|
|
468
|
+
// chant#2589 — path lexicons the target declares, read without running its config.
|
|
469
|
+
const pathLexicons = auditPathLexicons(options.path, isUrl);
|
|
444
470
|
|
|
445
471
|
// Detection lives in the lexicon plugins (each one's `detectTemplate`), so a
|
|
446
472
|
// lexicon that isn't installed can't claim its files. Every branch below
|
|
@@ -449,7 +475,7 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
|
|
|
449
475
|
// render the same way (#1623).
|
|
450
476
|
const plugins = options.plugins ?? (await loadAuditPlugins());
|
|
451
477
|
let candidates: RepoFile[];
|
|
452
|
-
let
|
|
478
|
+
let truncated: AuditTruncation | undefined;
|
|
453
479
|
if (isUrl) {
|
|
454
480
|
try {
|
|
455
481
|
// Fetch the whole repo's candidate files (all lexicons, not just CI) and
|
|
@@ -469,20 +495,18 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
|
|
|
469
495
|
// One walk, plugin-delegated detection. CI (path), Dockerfiles (name), and
|
|
470
496
|
// Helm charts (bundle) are special-cased by the classifier since content
|
|
471
497
|
// shape alone can't disambiguate them.
|
|
472
|
-
|
|
498
|
+
// Workspace members leave the root's audit only when a declaration exists
|
|
499
|
+
// (#2525 rule 3); without one this is a few existsSync calls (#2527).
|
|
500
|
+
const excludeDirs = await workspaceMemberDirs(options.path);
|
|
501
|
+
const walk = walkCandidates(options.path, { maxFiles: options.maxFiles, excludeDirs });
|
|
473
502
|
candidates = walk.files;
|
|
474
|
-
|
|
475
|
-
// #2527's warning release: the converged walk skips git-ignored files and,
|
|
476
|
-
// inside a project, child projects. Terraform state paths are TF023's own
|
|
477
|
-
// question and stay out of it. `candidates` is unchanged.
|
|
478
|
-
await warnDiscoveryChanges({
|
|
479
|
-
walker: "audit",
|
|
480
|
-
root: options.path,
|
|
481
|
-
files: candidates.filter((f) => !isTerraformStatePath(f.path)).map((f) => resolve(options.path, f.path)),
|
|
482
|
-
});
|
|
503
|
+
truncated = walkTruncation(walk);
|
|
483
504
|
}
|
|
484
|
-
//
|
|
485
|
-
//
|
|
505
|
+
// Every format but SARIF states a truncated scan in the report itself
|
|
506
|
+
// (#2528). SARIF has no slot chant fills for it, so there it goes to stderr.
|
|
507
|
+
const warnings: AuditWarning[] =
|
|
508
|
+
truncated && format === "sarif" ? [{ message: truncationNote(truncated, options.path) }] : [];
|
|
509
|
+
if (pathLexicons.notice !== undefined) warnings.push({ message: pathLexicons.notice });
|
|
486
510
|
const withWarnings = warnings.length > 0 ? { warnings } : {};
|
|
487
511
|
// A local target hands its directory to the classifier, so an input whose
|
|
488
512
|
// path names a directory carries the directory itself (#2217): terraform's
|
|
@@ -514,14 +538,18 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
|
|
|
514
538
|
const terraformStateFindings = auditTerraformState(candidates);
|
|
515
539
|
|
|
516
540
|
if (plugins.length === 0) {
|
|
517
|
-
const output =
|
|
541
|
+
const output =
|
|
542
|
+
format === "json"
|
|
543
|
+
? renderNoLexiconsJson(options.path, unclaimed, pathLexicons.paths, truncated)
|
|
544
|
+
: renderNoLexicons(options.path, unclaimed, pathLexicons.paths, truncated);
|
|
518
545
|
return { success: true, status: "no-lexicons", output, findings: [...secretsFindings, ...wranglerFindings, ...nginxFindings, ...terraformStateFindings], scanned: [], unclaimed, exitCode: NO_LEXICONS_EXIT_CODE, stream: format === "json" ? "stdout" : "stderr", ...withWarnings };
|
|
519
546
|
}
|
|
520
547
|
|
|
521
|
-
const missingLexiconNote = missingLexiconHint(unclaimed);
|
|
548
|
+
const missingLexiconNote = missingLexiconHint(unclaimed, pathLexicons.paths);
|
|
522
549
|
|
|
523
550
|
if (inputs.length === 0 && secretsFindings.length === 0 && wranglerFindings.length === 0 && nginxFindings.length === 0 && terraformStateFindings.length === 0) {
|
|
524
|
-
|
|
551
|
+
let output = `No auditable files found under ${options.path}.${missingLexiconNote ? ` ${missingLexiconNote}` : ""}`;
|
|
552
|
+
if (truncated) output += `\n\nNote: ${truncationNote(truncated, options.path)}`;
|
|
525
553
|
return { success: true, status: "ok", output, findings: [], scanned: [], unclaimed, exitCode: 0, ...withWarnings };
|
|
526
554
|
}
|
|
527
555
|
|
|
@@ -548,6 +576,7 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
|
|
|
548
576
|
|
|
549
577
|
if (tier === "merge-worthy") findings = findings.filter((f) => isMergeWorthy(f, catalog));
|
|
550
578
|
const notes = coverageNotes(inputs);
|
|
579
|
+
if (truncated) notes.unshift(truncationNote(truncated, options.path));
|
|
551
580
|
if (missingLexiconNote) notes.push(missingLexiconNote);
|
|
552
581
|
if (suppressionStats.count > 0) {
|
|
553
582
|
notes.push(
|
|
@@ -596,7 +625,7 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
|
|
|
596
625
|
switch (format) {
|
|
597
626
|
case "json": {
|
|
598
627
|
const snapshot = await buildSnapshot(options, scanned, isUrl);
|
|
599
|
-
output = JSON.stringify(buildReportJson(findings, { snapshot, toolVersion: options.toolVersion, catalog, unclaimed }), null, 2);
|
|
628
|
+
output = JSON.stringify(buildReportJson(findings, { snapshot, toolVersion: options.toolVersion, catalog, unclaimed, truncated }), null, 2);
|
|
600
629
|
break;
|
|
601
630
|
}
|
|
602
631
|
case "sarif":
|
|
@@ -607,7 +636,7 @@ export async function auditCommand(options: AuditCommandOptions): Promise<AuditC
|
|
|
607
636
|
break;
|
|
608
637
|
case "html": {
|
|
609
638
|
const snapshot = await buildSnapshot(options, scanned, isUrl);
|
|
610
|
-
output = renderHtml(findings, { files, resolveSha, resolveDigest, notes, snapshot, theme: options.theme, template: options.template, catalog });
|
|
639
|
+
output = renderHtml(findings, { files, resolveSha, resolveDigest, notes, snapshot, theme: options.theme, template: options.template, catalog, truncated });
|
|
611
640
|
break;
|
|
612
641
|
}
|
|
613
642
|
default:
|
|
@@ -106,6 +106,28 @@ export async function coverageReportCheck(plugin: LexiconPlugin | undefined): Pr
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* #2535 — a lexicon that supplies member kinds publishes them as data at a
|
|
111
|
+
* `./workspace-kinds` subpath (ws-031). The file must be JSON, validate as
|
|
112
|
+
* kind data, and ship in the package. A lexicon without the subpath passes
|
|
113
|
+
* vacuously, like `coverageReport()` above. The workspace module loads only
|
|
114
|
+
* here, when the check runs.
|
|
115
|
+
*/
|
|
116
|
+
export async function workspaceKindsCheck(dir: string): Promise<CheckItem> {
|
|
117
|
+
const { readPackageKinds, kindsFileShips } = await import("../../workspace/kinds");
|
|
118
|
+
const read = readPackageKinds(dir);
|
|
119
|
+
const problems = [...read.problems];
|
|
120
|
+
if (read.file && problems.length === 0 && !kindsFileShips(dir, read.file)) {
|
|
121
|
+
problems.push(`${basename(read.file)} is not covered by package.json "files", so the published package would not carry it`);
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
name: "Any ./workspace-kinds subpath holds valid kind data",
|
|
125
|
+
tier: 1,
|
|
126
|
+
pass: problems.length === 0,
|
|
127
|
+
detail: problems.length > 0 ? problems.join("; ") : read.file ? `${read.kinds.length} kind(s): ${read.kinds.map((k) => k.name).join(", ")}` : undefined,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
109
131
|
// ── Check runner ─────────────────────────────────────────────────────
|
|
110
132
|
|
|
111
133
|
/**
|
|
@@ -434,6 +456,8 @@ export async function checkLexicon(dir: string): Promise<CheckResult> {
|
|
|
434
456
|
// now states the fact directly via `coverageReport()`.
|
|
435
457
|
items.push(await coverageReportCheck(plugin));
|
|
436
458
|
|
|
459
|
+
items.push(await workspaceKindsCheck(dir));
|
|
460
|
+
|
|
437
461
|
// ── Tier 2: Recommended ────────────────────────────────────────
|
|
438
462
|
|
|
439
463
|
const pluginContent = readOr(join(dir, "src/plugin.ts"));
|
|
@@ -396,3 +396,33 @@ describe("package-type-module (#1421)", () => {
|
|
|
396
396
|
});
|
|
397
397
|
});
|
|
398
398
|
});
|
|
399
|
+
|
|
400
|
+
describe("doctorCommand in a declared workspace (#2535)", () => {
|
|
401
|
+
test("shows a member of kind other as a warning, and adds nothing outside a workspace", async () => {
|
|
402
|
+
await withTestDir(async (testDir) => {
|
|
403
|
+
mkdirSync(join(testDir, ".git"));
|
|
404
|
+
mkdirSync(join(testDir, "app"));
|
|
405
|
+
mkdirSync(join(testDir, "docs"));
|
|
406
|
+
writeFileSync(join(testDir, "app", "chant.config.json"), "{}");
|
|
407
|
+
const before = await doctorCommand(join(testDir, "app"));
|
|
408
|
+
expect(before.checks.filter((c) => c.name.startsWith("workspace-"))).toEqual([]);
|
|
409
|
+
|
|
410
|
+
writeFileSync(
|
|
411
|
+
join(testDir, "chant.workspace.json"),
|
|
412
|
+
JSON.stringify({
|
|
413
|
+
name: "acme",
|
|
414
|
+
schema: 1,
|
|
415
|
+
members: [
|
|
416
|
+
{ name: "app", dir: "app", kind: "chant" },
|
|
417
|
+
{ name: "docs", dir: "docs", kind: "other", because: "a static site" },
|
|
418
|
+
],
|
|
419
|
+
}),
|
|
420
|
+
);
|
|
421
|
+
const report = await doctorCommand(join(testDir, "app"));
|
|
422
|
+
const workspace = report.checks.filter((c) => c.name.startsWith("workspace-"));
|
|
423
|
+
expect(workspace).toEqual([
|
|
424
|
+
{ name: "workspace-WSP009", status: "warn", message: expect.stringMatching(/^member docs \(docs\) is kind other, which chant does not read: a static site \(.*chant\.workspace\.json:1\)$/) },
|
|
425
|
+
]);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
});
|