@intentius/chant 0.80.0 → 0.81.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 +31 -0
- package/dist/workspace/lineage-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +101 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -0
- package/dist/workspace/lineage-lock.d.ts +258 -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 +146 -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 +54 -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 +230 -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 +162 -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 +114 -0
- package/src/workspace/lineage-init.test.ts +231 -0
- package/src/workspace/lineage-init.ts +365 -0
- package/src/workspace/lineage-lock.test.ts +224 -0
- package/src/workspace/lineage-lock.ts +357 -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 +624 -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
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workspace checks behind member ledgers (#2538, #2524 D7, ws-036 and
|
|
3
|
+
* ws-037).
|
|
4
|
+
*
|
|
5
|
+
* - `WSP071`: every `chant` member that stamps ownership markers uses its own
|
|
6
|
+
* `ownership.stack`. Markers carry no member key, so the stack is what tells
|
|
7
|
+
* two members' resources and receipts apart.
|
|
8
|
+
* - `WSP072`: members that write the flat ledger layout never share an
|
|
9
|
+
* environment name. A member whose own chant is older than
|
|
10
|
+
* {@link MEMBER_LEDGER_FLOOR} still writes `<env>/...` at the top of
|
|
11
|
+
* `chant/lifecycle`, and so does the root member `.`; two flat writers with
|
|
12
|
+
* one environment name would write the same files.
|
|
13
|
+
* - `WSP073` (info): a member's ledger settings could not be read without
|
|
14
|
+
* running its config, so the two checks above could not compare it.
|
|
15
|
+
*
|
|
16
|
+
* The checks are pure functions over {@link MemberLedgerFacts}.
|
|
17
|
+
* {@link gatherLedgerFacts} collects those facts from a checkout, and
|
|
18
|
+
* `chant workspace check` gathers them before it runs {@link LEDGER_CHECKS}
|
|
19
|
+
* (#2641). WSP071 and WSP072 are fixed: a member that ignores either one
|
|
20
|
+
* writes its records or marks its resources as another member's. Member
|
|
21
|
+
* configs are read statically and never run.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
25
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
26
|
+
import { MEMBER_LEDGER_FLOOR } from "../../lifecycle/member-ledger";
|
|
27
|
+
import type { WorkspaceCheck, WorkspaceCheckContext, WorkspaceDiagnostic } from "../checks";
|
|
28
|
+
import { compareVersions, type Declaration } from "../declaration";
|
|
29
|
+
|
|
30
|
+
export const WSP_DISTINCT_STACKS = "WSP071";
|
|
31
|
+
export const WSP_FLAT_LEDGER_ENVIRONMENTS = "WSP072";
|
|
32
|
+
export const WSP_LEDGER_SETTINGS_UNREAD = "WSP073";
|
|
33
|
+
|
|
34
|
+
export interface LedgerFinding {
|
|
35
|
+
id: typeof WSP_DISTINCT_STACKS | typeof WSP_FLAT_LEDGER_ENVIRONMENTS;
|
|
36
|
+
severity: "error";
|
|
37
|
+
/** The members the finding is about, in declaration order. */
|
|
38
|
+
members: string[];
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** What the ledger checks need to know about one `chant` member. */
|
|
43
|
+
export interface MemberLedgerFacts {
|
|
44
|
+
name: string;
|
|
45
|
+
/** Relative to the workspace root; `"."` is the root member. */
|
|
46
|
+
dir: string;
|
|
47
|
+
/** `ownership.stack` when marking is on, else null. */
|
|
48
|
+
stack: string | null;
|
|
49
|
+
/** The environment names the member declares (`environments`, and a literal `ownership.env`). */
|
|
50
|
+
environments: string[];
|
|
51
|
+
/** The chant the member's own toolchain resolves, or null when none is installed. */
|
|
52
|
+
chantVersion: string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Why the member's ledger settings could not be read without running its
|
|
55
|
+
* config. Such a member has no stack and no environments here, so neither
|
|
56
|
+
* check compares it; `WSP073` reports it instead.
|
|
57
|
+
*/
|
|
58
|
+
unread?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** `WSP071`: no two members share an `ownership.stack`. */
|
|
62
|
+
export function checkDistinctStacks(facts: readonly MemberLedgerFacts[]): LedgerFinding[] {
|
|
63
|
+
const byStack = new Map<string, MemberLedgerFacts[]>();
|
|
64
|
+
for (const f of facts) {
|
|
65
|
+
if (f.stack === null) continue;
|
|
66
|
+
byStack.set(f.stack, [...(byStack.get(f.stack) ?? []), f]);
|
|
67
|
+
}
|
|
68
|
+
const findings: LedgerFinding[] = [];
|
|
69
|
+
for (const [stack, members] of byStack) {
|
|
70
|
+
if (members.length < 2) continue;
|
|
71
|
+
const names = members.map((m) => m.name);
|
|
72
|
+
findings.push({
|
|
73
|
+
id: WSP_DISTINCT_STACKS,
|
|
74
|
+
severity: "error",
|
|
75
|
+
members: names,
|
|
76
|
+
message:
|
|
77
|
+
`members ${names.join(", ")} all set ownership.stack to ${JSON.stringify(stack)}; ` +
|
|
78
|
+
`ownership markers carry no member name, so each member needs its own stack. ` +
|
|
79
|
+
`Rename all but one in their chant.config.ts (chant workspace init proposes names).`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return findings;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Whether `f` writes the flat ledger layout: the root member, or a member on a chant below the floor. */
|
|
86
|
+
export function writesFlatLedger(f: MemberLedgerFacts, floor: string = MEMBER_LEDGER_FLOOR): boolean {
|
|
87
|
+
if (f.dir === ".") return true;
|
|
88
|
+
if (f.chantVersion === null) return false;
|
|
89
|
+
try {
|
|
90
|
+
return compareVersions(f.chantVersion, floor) < 0;
|
|
91
|
+
} catch {
|
|
92
|
+
// A version chant cannot read says nothing about the layout.
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** `WSP072`: flat-layout writers never share an environment name. */
|
|
98
|
+
export function checkFlatLedgerEnvironments(
|
|
99
|
+
facts: readonly MemberLedgerFacts[],
|
|
100
|
+
floor: string = MEMBER_LEDGER_FLOOR,
|
|
101
|
+
): LedgerFinding[] {
|
|
102
|
+
const flat = facts.filter((f) => writesFlatLedger(f, floor));
|
|
103
|
+
const byEnv = new Map<string, MemberLedgerFacts[]>();
|
|
104
|
+
for (const f of flat) {
|
|
105
|
+
for (const env of new Set(f.environments)) byEnv.set(env, [...(byEnv.get(env) ?? []), f]);
|
|
106
|
+
}
|
|
107
|
+
const findings: LedgerFinding[] = [];
|
|
108
|
+
for (const [env, members] of byEnv) {
|
|
109
|
+
if (members.length < 2) continue;
|
|
110
|
+
const names = members.map((m) => m.name);
|
|
111
|
+
const why = members.map((m) => (m.dir === "." ? `${m.name} is the root member` : `${m.name} runs chant ${m.chantVersion}`));
|
|
112
|
+
findings.push({
|
|
113
|
+
id: WSP_FLAT_LEDGER_ENVIRONMENTS,
|
|
114
|
+
severity: "error",
|
|
115
|
+
members: names,
|
|
116
|
+
message:
|
|
117
|
+
`members ${names.join(", ")} ${names.length === 2 ? "both" : "all"} write the flat ledger layout and share the environment ${JSON.stringify(env)}, ` +
|
|
118
|
+
`so their releases, snapshots and runs land in the same files on chant/lifecycle (${why.join("; ")}). ` +
|
|
119
|
+
`Upgrade the members below chant ${floor}, which write under _members/<member>/, or rename the environment in one of them.`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return findings;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Both ledger checks, stack findings first. */
|
|
126
|
+
export function checkLedgers(facts: readonly MemberLedgerFacts[], floor: string = MEMBER_LEDGER_FLOOR): LedgerFinding[] {
|
|
127
|
+
return [...checkDistinctStacks(facts), ...checkFlatLedgerEnvironments(facts, floor)];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── As workspace checks ──────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A finding as a workspace diagnostic. It sits at the first member it names,
|
|
134
|
+
* in declaration order, and that member's entry is the one that can carry a
|
|
135
|
+
* `suppress` for a settable check.
|
|
136
|
+
*/
|
|
137
|
+
function toDiagnostic(check: WorkspaceCheck, ctx: WorkspaceCheckContext, f: LedgerFinding): WorkspaceDiagnostic {
|
|
138
|
+
const member = ctx.declaration.members.find((m) => m.name === f.members[0]);
|
|
139
|
+
return { checkId: check.id, severity: check.severity, message: f.message, entity: f.members[0], pointer: member?.pointer ?? "" };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The ledger checks, which read `ctx.facts.ledgers`. They find nothing when the facts were not gathered. */
|
|
143
|
+
export const LEDGER_CHECKS: readonly WorkspaceCheck[] = [
|
|
144
|
+
{
|
|
145
|
+
id: WSP_DISTINCT_STACKS,
|
|
146
|
+
name: "ownership-stack-shared",
|
|
147
|
+
description: "No two chant members set the same ownership.stack. Ownership markers carry no member name, so the stack tells members' resources and receipts apart.",
|
|
148
|
+
severity: "error",
|
|
149
|
+
configurable: false,
|
|
150
|
+
check(ctx) {
|
|
151
|
+
return checkDistinctStacks(ctx.facts?.ledgers ?? []).map((f) => toDiagnostic(this, ctx, f));
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: WSP_FLAT_LEDGER_ENVIRONMENTS,
|
|
156
|
+
name: "flat-ledger-environment-shared",
|
|
157
|
+
description: `No two members that write the flat ledger layout (the root member, and members on a chant older than ${MEMBER_LEDGER_FLOOR}) share an environment name.`,
|
|
158
|
+
severity: "error",
|
|
159
|
+
configurable: false,
|
|
160
|
+
check(ctx) {
|
|
161
|
+
return checkFlatLedgerEnvironments(ctx.facts?.ledgers ?? []).map((f) => toDiagnostic(this, ctx, f));
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: WSP_LEDGER_SETTINGS_UNREAD,
|
|
166
|
+
name: "ledger-settings-unread",
|
|
167
|
+
description: "A chant member's ownership and environments could not be read from its config without running it, so WSP071 and WSP072 could not compare it.",
|
|
168
|
+
severity: "info",
|
|
169
|
+
configurable: true,
|
|
170
|
+
check(ctx) {
|
|
171
|
+
return (ctx.facts?.ledgers ?? [])
|
|
172
|
+
.filter((f) => f.unread !== undefined)
|
|
173
|
+
.map((f) => ({
|
|
174
|
+
checkId: this.id,
|
|
175
|
+
severity: this.severity,
|
|
176
|
+
message: `member ${f.name}'s ownership and environments were not compared with other members': ${f.unread}`,
|
|
177
|
+
entity: f.name,
|
|
178
|
+
pointer: ctx.declaration.members.find((m) => m.name === f.name)?.pointer ?? "",
|
|
179
|
+
}));
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
// ── Facts from a checkout ────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The version of `@intentius/chant` that `dir`'s own toolchain resolves: the
|
|
188
|
+
* nearest `node_modules/@intentius/chant/package.json` walking up from `dir`,
|
|
189
|
+
* as Node's resolution would find it. Null when there is none.
|
|
190
|
+
*/
|
|
191
|
+
export function memberChantVersion(dir: string): string | null {
|
|
192
|
+
for (let d = resolve(dir); ; d = dirname(d)) {
|
|
193
|
+
const pkg = join(d, "node_modules", "@intentius", "chant", "package.json");
|
|
194
|
+
if (existsSync(pkg)) {
|
|
195
|
+
try {
|
|
196
|
+
const version = (JSON.parse(readFileSync(pkg, "utf-8")) as { version?: unknown }).version;
|
|
197
|
+
return typeof version === "string" ? version : null;
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (dirname(d) === d) return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The config fields the ledger checks read. */
|
|
207
|
+
const LEDGER_FIELDS = ["ownership", "environments"] as const;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Collect {@link MemberLedgerFacts} for every `chant` member of `declaration`,
|
|
211
|
+
* whose workspace root is `root`. `ownership` and `environments` are read
|
|
212
|
+
* from each member's config statically (../../config-static.ts), the way
|
|
213
|
+
* `chant audit` reads lexicons: the config is never run (#2641). A member
|
|
214
|
+
* whose settings can't be read that way gets {@link MemberLedgerFacts.unread}
|
|
215
|
+
* with the reason, and no stack or environments.
|
|
216
|
+
*/
|
|
217
|
+
export async function gatherLedgerFacts(root: string, declaration: Declaration): Promise<MemberLedgerFacts[]> {
|
|
218
|
+
const { readConfigFieldsStatically } = await import("../../config-static");
|
|
219
|
+
const { ChantConfigSchema, environmentNames, resolveOwnershipStack } = await import("../../config");
|
|
220
|
+
const facts: MemberLedgerFacts[] = [];
|
|
221
|
+
for (const m of declaration.members) {
|
|
222
|
+
if (m.kind !== "chant") continue;
|
|
223
|
+
const dir = join(root, m.dir);
|
|
224
|
+
const base = { name: m.name, dir: m.dir, chantVersion: memberChantVersion(dir) };
|
|
225
|
+
const read = readConfigFieldsStatically(dir, LEDGER_FIELDS);
|
|
226
|
+
// No config: the kind probe reports the member (WSP005).
|
|
227
|
+
if (read.status === "no-config") continue;
|
|
228
|
+
const file = relative(root, read.configPath).split(sep).join("/");
|
|
229
|
+
const unread = (reason: string) => facts.push({ ...base, stack: null, environments: [], unread: `${file} can't be read without running it (${reason})` });
|
|
230
|
+
if (read.status === "unknown") {
|
|
231
|
+
unread(read.reason);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const parsed = ChantConfigSchema.pick({ ownership: true, environments: true }).safeParse(read.fields);
|
|
235
|
+
if (!parsed.success) {
|
|
236
|
+
unread(`its ${String(parsed.error.issues[0]?.path[0] ?? "settings")} value is not what a chant config takes`);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const config = parsed.data;
|
|
240
|
+
const environments = [...(environmentNames(config.environments) ?? [])];
|
|
241
|
+
const literalEnv = config.ownership?.env;
|
|
242
|
+
if (typeof literalEnv === "string" && !environments.includes(literalEnv)) environments.push(literalEnv);
|
|
243
|
+
facts.push({ ...base, stack: resolveOwnershipStack(config) ?? null, environments });
|
|
244
|
+
}
|
|
245
|
+
return facts;
|
|
246
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The member-link checks, WSP091 to WSP097 (#2539; #2524 D6, ws-008).
|
|
3
|
+
*
|
|
4
|
+
* `chant workspace check` resolves every declared link in source: the
|
|
5
|
+
* producer's outputs come from its kind, and a `chant` producer's are read
|
|
6
|
+
* from its TypeScript without running it (`../member-handles.ts`). Nothing
|
|
7
|
+
* here reaches the network or starts a process. The rows themselves come
|
|
8
|
+
* from `../links.ts`, which `chant workspace graph` shares.
|
|
9
|
+
*
|
|
10
|
+
* | Id | Fails when |
|
|
11
|
+
* |---|---|
|
|
12
|
+
* | WSP091 | a link names no member, an example group, or its own member (fixed) |
|
|
13
|
+
* | WSP092 | a link's kind is not a link kind chant knows (fixed) |
|
|
14
|
+
* | WSP093 | the producer exposes no output of that name, as when it was renamed |
|
|
15
|
+
* | WSP094 | the producer's outputs can't be read in source, so the link is kept unresolved |
|
|
16
|
+
* | WSP095 | an inferred join is ambiguous: a parameter matches two or more outputs |
|
|
17
|
+
* | WSP096 | a consumer states the same link twice |
|
|
18
|
+
* | WSP097 | an entry lists `outputs` for a kind that doesn't take them from the entry |
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { WorkspaceCheck, WorkspaceCheckContext, WorkspaceDiagnostic } from "../checks";
|
|
22
|
+
import type { Member } from "../declaration";
|
|
23
|
+
import { sourceMemberHandles } from "../member-handles";
|
|
24
|
+
import { DEFAULT_LINK_KIND, LINK_KINDS, linkTargetProblem, resolveLinks, type AmbiguousRow, type LinkRow, type LinkTableRow } from "../links";
|
|
25
|
+
|
|
26
|
+
const tables = new WeakMap<WorkspaceCheckContext, LinkTableRow[]>();
|
|
27
|
+
|
|
28
|
+
/** The link rows for a check run, resolved once and shared by every link check. */
|
|
29
|
+
export function linkTable(ctx: WorkspaceCheckContext): LinkTableRow[] {
|
|
30
|
+
let rows = tables.get(ctx);
|
|
31
|
+
if (!rows) {
|
|
32
|
+
rows = resolveLinks(ctx.declaration, sourceMemberHandles(ctx.declaration, ctx.tree, ctx.groups, ctx.kinds));
|
|
33
|
+
tables.set(ctx, rows);
|
|
34
|
+
}
|
|
35
|
+
return rows;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const declaredRows = (ctx: WorkspaceCheckContext) => linkTable(ctx).filter((r): r is LinkRow => r.status !== "ambiguous" && r.origin === "declared");
|
|
39
|
+
|
|
40
|
+
function linkFinding(check: WorkspaceCheck, row: LinkRow, field: string, message: string): WorkspaceDiagnostic {
|
|
41
|
+
return { checkId: check.id, severity: check.severity, message, entity: row.consumer, pointer: `${row.pointer}/${field}` };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const describeLink = (row: LinkRow) => `${row.consumer}'s link to ${row.producer} output ${row.output}`;
|
|
45
|
+
|
|
46
|
+
export const LINK_CHECKS: readonly WorkspaceCheck[] = [
|
|
47
|
+
{
|
|
48
|
+
id: "WSP091",
|
|
49
|
+
name: "link-target-unknown",
|
|
50
|
+
description: "Every member link names another member of the workspace. A link to an unknown name, an example group or the consumer itself fails closed.",
|
|
51
|
+
severity: "error",
|
|
52
|
+
configurable: false,
|
|
53
|
+
check(ctx) {
|
|
54
|
+
return ctx.declaration.members.flatMap((m) =>
|
|
55
|
+
m.links.flatMap((l) => {
|
|
56
|
+
const problem = linkTargetProblem(ctx.declaration, m, l);
|
|
57
|
+
return problem ? [{ checkId: this.id, severity: this.severity, message: problem, entity: m.name, pointer: `${l.pointer}/member` }] : [];
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "WSP092",
|
|
64
|
+
name: "link-kind-unknown",
|
|
65
|
+
description: `Every member link's kind is one chant knows. Unknown link kinds fail closed; ${DEFAULT_LINK_KIND} is the default and the only one so far.`,
|
|
66
|
+
severity: "error",
|
|
67
|
+
configurable: false,
|
|
68
|
+
check(ctx) {
|
|
69
|
+
return ctx.declaration.members.flatMap((m) =>
|
|
70
|
+
m.links
|
|
71
|
+
.filter((l) => l.kind !== null && !(LINK_KINDS as readonly string[]).includes(l.kind))
|
|
72
|
+
.map((l) => ({
|
|
73
|
+
checkId: this.id,
|
|
74
|
+
severity: this.severity,
|
|
75
|
+
message: `member ${m.name}'s link to ${l.member} has kind ${l.kind}, which is not a link kind chant knows; known link kinds: ${LINK_KINDS.join(", ")}`,
|
|
76
|
+
entity: m.name,
|
|
77
|
+
pointer: `${l.pointer}/kind`,
|
|
78
|
+
})),
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "WSP093",
|
|
84
|
+
name: "link-output-missing",
|
|
85
|
+
description: "Every member link names an output its producer exposes, matched exactly. A producer that renames an output fails this for every consumer that links to the old name.",
|
|
86
|
+
severity: "error",
|
|
87
|
+
configurable: true,
|
|
88
|
+
check(ctx) {
|
|
89
|
+
return declaredRows(ctx)
|
|
90
|
+
.filter((r) => r.status === "missing")
|
|
91
|
+
.map((r) => linkFinding(this, r, "output", `${describeLink(r)} does not resolve: ${r.reason}`));
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "WSP094",
|
|
96
|
+
name: "link-unresolved",
|
|
97
|
+
description: "A member link whose producer's outputs can't be read in source is kept, unresolved, and reported.",
|
|
98
|
+
severity: "info",
|
|
99
|
+
configurable: true,
|
|
100
|
+
check(ctx) {
|
|
101
|
+
return declaredRows(ctx)
|
|
102
|
+
.filter((r) => r.status === "unresolved")
|
|
103
|
+
.map((r) => linkFinding(this, r, "output", `${describeLink(r)} is kept unresolved: ${r.reason}`));
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "WSP095",
|
|
108
|
+
name: "join-ambiguous",
|
|
109
|
+
description: "No inferred join is ambiguous: a parameter that matches outputs of two producers (or two outputs of one) needs a declared link saying which.",
|
|
110
|
+
severity: "warning",
|
|
111
|
+
configurable: true,
|
|
112
|
+
check(ctx) {
|
|
113
|
+
const byName = new Map(ctx.declaration.members.map((m) => [m.name, m]));
|
|
114
|
+
return linkTable(ctx)
|
|
115
|
+
.filter((r): r is AmbiguousRow => r.status === "ambiguous")
|
|
116
|
+
.map((r) => {
|
|
117
|
+
const choices = r.candidates.map((c) => `${c.producer} output ${c.output} (${c.label})`).join(", ");
|
|
118
|
+
const first = r.candidates[0];
|
|
119
|
+
return {
|
|
120
|
+
checkId: this.id,
|
|
121
|
+
severity: this.severity,
|
|
122
|
+
message: `${r.reason}: ${choices}. Declare the one it reads, such as { "member": "${first.producer}", "output": "${first.output}" } in ${r.consumer}'s links`,
|
|
123
|
+
entity: r.consumer,
|
|
124
|
+
pointer: byName.get(r.consumer)!.pointer,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "WSP096",
|
|
131
|
+
name: "link-duplicate",
|
|
132
|
+
description: "A link is stated once: no consumer lists the same member and output twice.",
|
|
133
|
+
severity: "error",
|
|
134
|
+
configurable: true,
|
|
135
|
+
check(ctx) {
|
|
136
|
+
const out: WorkspaceDiagnostic[] = [];
|
|
137
|
+
for (const m of ctx.declaration.members) {
|
|
138
|
+
const seen = new Map<string, string>();
|
|
139
|
+
for (const l of m.links) {
|
|
140
|
+
const key = `${l.member}\0${l.output}\0${l.kind ?? DEFAULT_LINK_KIND}`;
|
|
141
|
+
const first = seen.get(key);
|
|
142
|
+
if (first) {
|
|
143
|
+
out.push({
|
|
144
|
+
checkId: this.id,
|
|
145
|
+
severity: this.severity,
|
|
146
|
+
message: `member ${m.name} links to ${l.member} output ${l.output} twice; the first is at ${first}`,
|
|
147
|
+
entity: m.name,
|
|
148
|
+
pointer: l.pointer,
|
|
149
|
+
});
|
|
150
|
+
} else {
|
|
151
|
+
seen.set(key, l.pointer);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "WSP097",
|
|
160
|
+
name: "outputs-not-listable",
|
|
161
|
+
description: "An entry lists outputs only when its kind takes them from the entry: other, or a kind from a package. A chant member's outputs are read from its source, and a nested workspace exposes none.",
|
|
162
|
+
severity: "error",
|
|
163
|
+
configurable: true,
|
|
164
|
+
check(ctx) {
|
|
165
|
+
return ctx.declaration.members
|
|
166
|
+
.filter((m): m is Member & { outputs: string[] } => m.outputs !== null)
|
|
167
|
+
.flatMap((m) => {
|
|
168
|
+
const kind = ctx.kinds.get(m.kind);
|
|
169
|
+
if (!kind || kind.outputs.from === "declared") return [];
|
|
170
|
+
const why = kind.outputs.from === "chant-source" ? "reads a member's outputs from its source" : "exposes no outputs";
|
|
171
|
+
return [
|
|
172
|
+
{
|
|
173
|
+
checkId: this.id,
|
|
174
|
+
severity: this.severity,
|
|
175
|
+
message: `member ${m.name} lists outputs, and kind ${m.kind} ${why}; remove the list`,
|
|
176
|
+
entity: m.name,
|
|
177
|
+
pointer: `${m.pointer}/outputs`,
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
];
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WSP081, WSP082 and WSP083 (#2542; ws-040, ws-042): one declarer per
|
|
3
|
+
* generated file, generated files inside their member or on a forge path,
|
|
4
|
+
* and linked members that share an environment name.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
8
|
+
import { readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { runDeclarationChecks } from "../checks";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import { readDeclaration } from "../declaration";
|
|
12
|
+
import { recordGeneratedFiles } from "../member-pipeline";
|
|
13
|
+
import { workingTree } from "../tree";
|
|
14
|
+
import { twoMemberWorkspace } from "../__fixtures__/two-member-workspace";
|
|
15
|
+
import {
|
|
16
|
+
checkGeneratedPlacement,
|
|
17
|
+
checkLinkedEnvironments,
|
|
18
|
+
checkOneDeclarer,
|
|
19
|
+
checkPipelines,
|
|
20
|
+
gatherPipelineFacts,
|
|
21
|
+
isForgePath,
|
|
22
|
+
type MemberPipelineFacts,
|
|
23
|
+
} from "./pipelines";
|
|
24
|
+
|
|
25
|
+
const facts = (name: string, dir: string, generated: string[] = [], environments: string[] = []): MemberPipelineFacts => ({
|
|
26
|
+
name,
|
|
27
|
+
dir,
|
|
28
|
+
generated,
|
|
29
|
+
environments,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("isForgePath: the narrow exemption", () => {
|
|
33
|
+
test.each([".github/workflows/a.yml", ".forgejo/workflows/a.yaml", ".gitea/workflows/a.yml", ".gitlab-ci.yml", ".gitlab/ci/api.gitlab-ci.yml"])("%s is a forge path", (p) => {
|
|
34
|
+
expect(isForgePath(p)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
test.each([".github/CODEOWNERS", ".github/workflows/nested/a.yml", "ci/a.yml", "docs/.gitlab-ci.yml", ".github/actions/x/action.yml"])("%s is not", (p) => {
|
|
37
|
+
expect(isForgePath(p)).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("WSP081: one declarer per generated file", () => {
|
|
42
|
+
test("two members declaring one file is an error naming both", () => {
|
|
43
|
+
const findings = checkOneDeclarer([
|
|
44
|
+
facts("api", "services/api", [".github/workflows/deploy.yml"]),
|
|
45
|
+
facts("web", "apps/web", [".github/workflows/deploy.yml", ".github/workflows/chant-web-prod.yml"]),
|
|
46
|
+
]);
|
|
47
|
+
expect(findings).toHaveLength(1);
|
|
48
|
+
expect(findings[0]).toMatchObject({ id: "WSP081", severity: "error", members: ["api", "web"], path: ".github/workflows/deploy.yml" });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("each member with its own file is fine", () => {
|
|
52
|
+
expect(checkOneDeclarer([facts("api", "services/api", ["a.yml"]), facts("web", "apps/web", ["b.yml"])])).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("WSP082: generated files sit in the member or on a forge path", () => {
|
|
57
|
+
test("a file in another member's directory is an error", () => {
|
|
58
|
+
const findings = checkGeneratedPlacement([facts("api", "services/api", ["services/api/gen.ts", ".github/workflows/x.yml", "apps/web/gen.ts"])]);
|
|
59
|
+
expect(findings).toEqual([expect.objectContaining({ id: "WSP082", members: ["api"], path: "apps/web/gen.ts" })]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("the root member may declare anything", () => {
|
|
63
|
+
expect(checkGeneratedPlacement([facts("platform", ".", ["anything/at/all.yml"])])).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("WSP083: linked members share an environment name", () => {
|
|
68
|
+
const members = [facts("api", "services/api", [], ["staging", "prod"]), facts("web", "apps/web", [], ["stage", "production"]), facts("jobs", "jobs", [], ["prod"])];
|
|
69
|
+
|
|
70
|
+
test("a link between members with no common name is a warning", () => {
|
|
71
|
+
const findings = checkLinkedEnvironments(members, [{ consumer: "web", producer: "api" }]);
|
|
72
|
+
expect(findings).toEqual([
|
|
73
|
+
expect.objectContaining({ id: "WSP083", severity: "warning", members: ["api", "web"], message: expect.stringMatching(/api \(prod, staging\) and web \(production, stage\)/) }),
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("one shared name is enough, and a member with no environments is skipped", () => {
|
|
78
|
+
expect(checkLinkedEnvironments(members, [{ consumer: "jobs", producer: "api" }])).toEqual([]);
|
|
79
|
+
expect(checkLinkedEnvironments([...members, facts("docs", "docs")], [{ consumer: "docs", producer: "api" }])).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("members that aren't linked are never compared", () => {
|
|
83
|
+
expect(checkLinkedEnvironments(members, [])).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("gathering from a checkout", () => {
|
|
88
|
+
let repo: string | undefined;
|
|
89
|
+
afterEach(() => {
|
|
90
|
+
if (repo) rmSync(repo, { recursive: true, force: true });
|
|
91
|
+
repo = undefined;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("each member's generated-file record is read, with its environments", () => {
|
|
95
|
+
repo = twoMemberWorkspace();
|
|
96
|
+
recordGeneratedFiles(join(repo, "services/api"), [{ path: ".github/workflows/chant-api-staging.yml", command: "c", env: "staging" }]);
|
|
97
|
+
recordGeneratedFiles(join(repo, "apps/web"), [
|
|
98
|
+
{ path: ".github/workflows/chant-api-staging.yml", command: "c", env: "staging" },
|
|
99
|
+
{ path: ".github/workflows/chant-web-prod.yml", command: "c", env: "prod" },
|
|
100
|
+
]);
|
|
101
|
+
const gathered = gatherPipelineFacts(repo, readDeclaration(workingTree(repo)));
|
|
102
|
+
expect(gathered).toEqual([
|
|
103
|
+
{ name: "api", dir: "services/api", generated: [".github/workflows/chant-api-staging.yml"], environments: ["staging"] },
|
|
104
|
+
{ name: "web", dir: "apps/web", generated: [".github/workflows/chant-api-staging.yml", ".github/workflows/chant-web-prod.yml"], environments: ["staging", "prod"] },
|
|
105
|
+
]);
|
|
106
|
+
expect(checkPipelines(gathered).map((f) => `${f.id}:${f.path}`)).toEqual(["WSP081:.github/workflows/chant-api-staging.yml"]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("WSP083 reads the declared member links: two linked members with no shared environment name (#2539)", async () => {
|
|
110
|
+
repo = twoMemberWorkspace();
|
|
111
|
+
const file = join(repo, "chant.workspace.json");
|
|
112
|
+
const decl = JSON.parse(readFileSync(file, "utf-8")) as { members: Record<string, unknown>[] };
|
|
113
|
+
decl.members.find((m) => m.name === "web")!.links = [{ member: "api", output: "ApiUrl" }];
|
|
114
|
+
writeFileSync(file, JSON.stringify(decl, null, 2));
|
|
115
|
+
writeFileSync(join(repo, "services/api/src/infra.ts"), `import { output } from "@intentius/chant-lexicon-aws";\nexport const url = output("u", "ApiUrl");\n`);
|
|
116
|
+
recordGeneratedFiles(join(repo, "services/api"), [{ path: "services/api/ci/api-staging.yml", command: "c", env: "staging" }]);
|
|
117
|
+
recordGeneratedFiles(join(repo, "apps/web"), [{ path: "apps/web/ci/web-production.yml", command: "c", env: "production" }]);
|
|
118
|
+
const report = await runDeclarationChecks(repo);
|
|
119
|
+
expect(report.diagnostics.map((d) => `${d.ruleId}:${d.severity}`)).toEqual(["WSP083:warning"]);
|
|
120
|
+
expect(report.diagnostics[0].message).toMatch(/api \(staging\) and web \(production\)/);
|
|
121
|
+
|
|
122
|
+
// Without the link, the two are never compared.
|
|
123
|
+
delete decl.members.find((m) => m.name === "web")!.links;
|
|
124
|
+
writeFileSync(file, JSON.stringify(decl, null, 2));
|
|
125
|
+
expect((await runDeclarationChecks(repo)).diagnostics).toEqual([]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("the chant repository's own declaration has no findings (#2557)", () => {
|
|
129
|
+
const root = resolve(__dirname, "../../../../..");
|
|
130
|
+
const declaration = readDeclaration(workingTree(root));
|
|
131
|
+
expect(checkPipelines(gatherPipelineFacts(root, declaration))).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
});
|