@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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify a change against the policy at its base (#2524 D5, Threat model).
|
|
3
|
+
*
|
|
4
|
+
* This is what CI runs on a protected branch: every commit in `base..head` is
|
|
5
|
+
* asked of the attestors with the policy read at base, so nothing the change
|
|
6
|
+
* does to its own copy of the policy counts. A change that edits the policy
|
|
7
|
+
* (the signers file or `.chant/trust.json`) is a protected write: every commit
|
|
8
|
+
* that touches those files must be attested by a signer the base policy
|
|
9
|
+
* trusts, and by an admin when the base policy grants the admin role.
|
|
10
|
+
*
|
|
11
|
+
* Attestation is opt-in (#2525 rule 6). Without a signers file at base the
|
|
12
|
+
* check passes and says so, unless `--require attested` asks for more.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
import { type CommitAttestor, type ProvenanceLevel } from "./attestor";
|
|
17
|
+
import { policyAtBase, commitProvenance, resolveBase, type BaseSource, type RecordProvenance } from "./provenance";
|
|
18
|
+
import { policyWriters, protectedPaths, type ExcludedSigner, type TrustPolicy } from "./policy";
|
|
19
|
+
|
|
20
|
+
export interface CommitVerdict extends RecordProvenance {
|
|
21
|
+
commit: string;
|
|
22
|
+
subject: string;
|
|
23
|
+
/** A merge that changes nothing of its own; its parents are checked instead. */
|
|
24
|
+
skipped?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ProtectedWrite {
|
|
28
|
+
commit: string;
|
|
29
|
+
paths: string[];
|
|
30
|
+
level: ProvenanceLevel;
|
|
31
|
+
principal?: string;
|
|
32
|
+
allowed: boolean;
|
|
33
|
+
reason: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ChangeReport {
|
|
37
|
+
base: string | null;
|
|
38
|
+
baseFrom: BaseSource | null;
|
|
39
|
+
head: string | null;
|
|
40
|
+
policy: {
|
|
41
|
+
active: boolean;
|
|
42
|
+
signersPath: string;
|
|
43
|
+
principals: string[];
|
|
44
|
+
excluded: ExcludedSigner[];
|
|
45
|
+
writers: string[];
|
|
46
|
+
problems: string[];
|
|
47
|
+
};
|
|
48
|
+
require: ProvenanceLevel | null;
|
|
49
|
+
commits: CommitVerdict[];
|
|
50
|
+
protectedPaths: string[];
|
|
51
|
+
/** Protected paths whose content differs between the merge base and head. */
|
|
52
|
+
protectedChanged: string[];
|
|
53
|
+
protectedWrites: ProtectedWrite[];
|
|
54
|
+
notes: string[];
|
|
55
|
+
failures: string[];
|
|
56
|
+
ok: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface VerifyOptions {
|
|
60
|
+
repo: string;
|
|
61
|
+
base?: string;
|
|
62
|
+
head?: string;
|
|
63
|
+
require?: ProvenanceLevel;
|
|
64
|
+
attestors: readonly CommitAttestor[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function git(repo: string, args: string[]): string {
|
|
68
|
+
return execFileSync("git", args, { cwd: repo, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 256 * 1024 * 1024 });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Commits in `range` that change any of `paths`, each with the paths it
|
|
73
|
+
* changes. Merges count only for what they change beyond every parent.
|
|
74
|
+
*/
|
|
75
|
+
export function commitsTouching(repo: string, range: string, paths: string[]): Map<string, string[]> {
|
|
76
|
+
const out = new Map<string, string[]>();
|
|
77
|
+
const log = git(repo, ["--literal-pathspecs", "log", "-c", "--name-only", "--no-renames", "--format=%x00%H", range, "--", ...paths]);
|
|
78
|
+
const wanted = new Set(paths);
|
|
79
|
+
let commit = "";
|
|
80
|
+
for (const line of log.split("\n")) {
|
|
81
|
+
if (line.startsWith("\0")) commit = line.slice(1).trim();
|
|
82
|
+
else if (line && wanted.has(line)) out.set(commit, [...(out.get(commit) ?? []), line]);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Whether a merge commit changes nothing beyond what its parents bring. */
|
|
88
|
+
function mergeWithoutOwnChanges(repo: string, commit: string, parents: number): boolean {
|
|
89
|
+
if (parents < 2) return false;
|
|
90
|
+
return git(repo, ["diff-tree", "-c", "--name-only", "--no-commit-id", "-r", commit]).trim() === "";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function verifyChange(opts: VerifyOptions): ChangeReport {
|
|
94
|
+
const { repo } = opts;
|
|
95
|
+
const base = resolveBase(repo, opts.base);
|
|
96
|
+
const policy: TrustPolicy = policyAtBase(repo, base);
|
|
97
|
+
const report: ChangeReport = {
|
|
98
|
+
base: base.commit,
|
|
99
|
+
baseFrom: base.from,
|
|
100
|
+
head: null,
|
|
101
|
+
policy: {
|
|
102
|
+
active: policy.active,
|
|
103
|
+
signersPath: policy.signersPath,
|
|
104
|
+
principals: [...new Set(policy.signers.map((s) => s.principal))].sort(),
|
|
105
|
+
excluded: policy.excluded,
|
|
106
|
+
writers: [...policyWriters(policy)].sort(),
|
|
107
|
+
problems: policy.problems,
|
|
108
|
+
},
|
|
109
|
+
require: opts.require ?? null,
|
|
110
|
+
commits: [],
|
|
111
|
+
protectedPaths: protectedPaths(policy),
|
|
112
|
+
protectedChanged: [],
|
|
113
|
+
protectedWrites: [],
|
|
114
|
+
notes: [],
|
|
115
|
+
failures: [],
|
|
116
|
+
ok: false,
|
|
117
|
+
};
|
|
118
|
+
const headRev = opts.head ?? "HEAD";
|
|
119
|
+
let head: string | undefined;
|
|
120
|
+
try {
|
|
121
|
+
head = headRev.startsWith("-") ? undefined : git(repo, ["rev-parse", "--verify", "--quiet", `${headRev}^{commit}`]).trim() || undefined;
|
|
122
|
+
} catch {
|
|
123
|
+
head = undefined;
|
|
124
|
+
}
|
|
125
|
+
if (!base.commit) report.failures.push(base.problem ?? "no base revision");
|
|
126
|
+
if (!head) report.failures.push(`--head ${headRev} names no commit in this repository`);
|
|
127
|
+
if (!base.commit || !head) return report;
|
|
128
|
+
report.head = head;
|
|
129
|
+
if (policy.problems.length > 0) {
|
|
130
|
+
report.failures.push(...policy.problems.map((p) => `the policy at base cannot be read, so nothing verifies: ${p}`));
|
|
131
|
+
return report;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const mergeBase = git(repo, ["merge-base", base.commit, head]).trim();
|
|
135
|
+
const changedPaths = git(repo, ["--literal-pathspecs", "diff", "--name-only", "--no-renames", mergeBase, head, "--", ...report.protectedPaths])
|
|
136
|
+
.split("\n")
|
|
137
|
+
.filter(Boolean);
|
|
138
|
+
report.protectedChanged = changedPaths;
|
|
139
|
+
|
|
140
|
+
if (!policy.active) {
|
|
141
|
+
report.notes.push(`no signers file (${policy.signersPath}) at base ${base.commit.slice(0, 8)}: attestation is off and nothing is verified`);
|
|
142
|
+
if (changedPaths.includes(policy.signersPath)) {
|
|
143
|
+
report.notes.push(`this change adds ${policy.signersPath}; it applies to changes made after it is merged, never to this one`);
|
|
144
|
+
}
|
|
145
|
+
if (opts.require) report.failures.push(`--require ${opts.require} was given, and there is no signers file at base to attest anything`);
|
|
146
|
+
report.ok = report.failures.length === 0;
|
|
147
|
+
return report;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Every commit in the change, judged by the policy at base.
|
|
151
|
+
const listing = git(repo, ["rev-list", "--reverse", "--parents", `${base.commit}..${head}`]).split("\n").filter(Boolean);
|
|
152
|
+
const verdicts = new Map<string, CommitVerdict>();
|
|
153
|
+
for (const line of listing) {
|
|
154
|
+
const [commit, ...parents] = line.split(" ");
|
|
155
|
+
const subject = git(repo, ["log", "-1", "--format=%s", commit]).trim();
|
|
156
|
+
const p = commitProvenance(repo, policy, commit, opts.attestors);
|
|
157
|
+
const v: CommitVerdict = { ...p, commit, subject };
|
|
158
|
+
if (p.level !== "attested" && mergeWithoutOwnChanges(repo, commit, parents.length)) {
|
|
159
|
+
v.skipped = "a merge with no changes of its own; the commits it brings are checked instead";
|
|
160
|
+
}
|
|
161
|
+
verdicts.set(commit, v);
|
|
162
|
+
report.commits.push(v);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Protected writes: every commit that touches the policy needs a writer's signature.
|
|
166
|
+
if (changedPaths.length > 0) {
|
|
167
|
+
const writers = policyWriters(policy);
|
|
168
|
+
const touching = commitsTouching(repo, `${base.commit}..${head}`, report.protectedPaths);
|
|
169
|
+
if (touching.size === 0) {
|
|
170
|
+
report.failures.push(`the policy files ${changedPaths.join(", ")} differ from base, and no commit in the change accounts for it`);
|
|
171
|
+
}
|
|
172
|
+
for (const [commit, paths] of touching) {
|
|
173
|
+
const v = verdicts.get(commit) ?? { ...commitProvenance(repo, policy, commit, opts.attestors), commit, subject: "" };
|
|
174
|
+
const allowed = v.level === "attested" && v.principal !== undefined && writers.has(v.principal);
|
|
175
|
+
const who = policy.roles.admin?.length ? "an admin the policy at base names" : "a signer the policy at base lists";
|
|
176
|
+
const reason = allowed
|
|
177
|
+
? `signed by ${v.principal}, ${who.replace(/^an? /, "")}`
|
|
178
|
+
: v.level === "attested"
|
|
179
|
+
? `signed by ${v.principal}, who is not ${who}`
|
|
180
|
+
: `${paths.join(", ")} is protected and needs a signature by ${who}: ${v.reason}`;
|
|
181
|
+
report.protectedWrites.push({ commit, paths, level: v.level, ...(v.principal ? { principal: v.principal } : {}), allowed, reason });
|
|
182
|
+
if (!allowed) report.failures.push(`${commit.slice(0, 8)} changes ${paths.join(", ")}: ${reason}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (opts.require) {
|
|
187
|
+
for (const v of report.commits) {
|
|
188
|
+
if (v.skipped || v.level === opts.require) continue;
|
|
189
|
+
report.failures.push(`${v.commit.slice(0, 8)} is ${v.level}, and --require ${opts.require} was given: ${v.reason}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
report.ok = report.failures.length === 0;
|
|
193
|
+
return report;
|
|
194
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which chant reads the declaration (#2536; #2524 D15, ws-021): the root's
|
|
3
|
+
* chant, named by a `@intentius/chant` pin; without a pin, the reader's own
|
|
4
|
+
* chant, if it meets `minReader`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
11
|
+
import { cleanScratch, declaration, REPO, repo } from "./__fixtures__/contract-repo";
|
|
12
|
+
import { readerVersion } from "./declaration";
|
|
13
|
+
import { workspaceGraph } from "./graph-cli";
|
|
14
|
+
import { runChecks } from "./lineage-check";
|
|
15
|
+
import { listWorkspace } from "./ls";
|
|
16
|
+
import { workspaceStatus } from "./status";
|
|
17
|
+
import { handToRootChant, HANDED_TO_ROOT_ENV, locateWorkspace, rootChantBin } from "./which-chant";
|
|
18
|
+
|
|
19
|
+
afterAll(cleanScratch);
|
|
20
|
+
|
|
21
|
+
const MAIN = join(REPO, "packages", "core", "src", "cli", "main.ts");
|
|
22
|
+
const LOADER = pathToFileURL(join(REPO, "node_modules", "tsx", "dist", "loader.mjs")).href;
|
|
23
|
+
|
|
24
|
+
/** An installed @intentius/chant at `version` whose bin reports how it was called, and exits 7. */
|
|
25
|
+
const installedChant = (version: string) => ({
|
|
26
|
+
"node_modules/@intentius/chant/package.json": JSON.stringify({ name: "@intentius/chant", version, bin: { chant: "bin/chant" } }),
|
|
27
|
+
"node_modules/@intentius/chant/bin/chant": { text: `#!/bin/sh\necho "root chant ${version}: $* ($${HANDED_TO_ROOT_ENV})"\nexit 7\n`, mode: 0o755 },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const pins = (version: string) => ({ pins: [{ package: "@intentius/chant", version }] });
|
|
31
|
+
|
|
32
|
+
/** The error code each read-contract command gives for the workspace at `cwd`, or "ok". */
|
|
33
|
+
async function codes(cwd: string): Promise<string[]> {
|
|
34
|
+
const ls = listWorkspace({ cwd });
|
|
35
|
+
const graph = (await workspaceGraph({ cwd })).doc;
|
|
36
|
+
const status = await workspaceStatus({ cwd, env: "dev", readLedger: async () => ({ records: [], malformed: 0 }) });
|
|
37
|
+
const check = await runChecks(cwd);
|
|
38
|
+
const checkCode = "error" in check ? check.error.code : (check.declaration?.diagnostics.find((d) => d.ruleId === "WSP001")?.code ?? "ok");
|
|
39
|
+
return [...[ls, graph, status].map((d) => ("error" in d ? d.error.code : "ok")), checkCode];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function chant(cwd: string, ...args: string[]) {
|
|
43
|
+
return spawnSync(process.execPath, ["--import", LOADER, MAIN, ...args], {
|
|
44
|
+
cwd,
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
timeout: 120_000,
|
|
47
|
+
env: { ...process.env, TSX_DISABLE_CACHE: "1", NO_COLOR: "1", [HANDED_TO_ROOT_ENV]: "" },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("a root that pins no chant", () => {
|
|
52
|
+
test("the reader's own chant reads the declaration when it meets minReader", async () => {
|
|
53
|
+
const root = repo({ "chant.workspace.json": declaration([], { minReader: readerVersion() }) });
|
|
54
|
+
expect(await codes(root)).toEqual(["ok", "ok", "ok", "ok"]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("and refuses with reader-too-old when it doesn't", async () => {
|
|
58
|
+
const root = repo({ "chant.workspace.json": declaration([], { minReader: "999.0.0" }) });
|
|
59
|
+
expect(await codes(root)).toEqual(["reader-too-old", "reader-too-old", "reader-too-old", "reader-too-old"]);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("a root that pins a chant", () => {
|
|
64
|
+
test("the pinned version reads it when it is this chant", async () => {
|
|
65
|
+
const root = repo({ "chant.workspace.json": declaration([], pins(readerVersion())) });
|
|
66
|
+
expect(await codes(root)).toEqual(["ok", "ok", "ok", "ok"]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("any other chant refuses with root-chant-required, before minReader and the schema", async () => {
|
|
70
|
+
const root = repo({ "chant.workspace.json": declaration([], { ...pins("0.0.1"), minReader: "999.0.0", "x-new": true, newField: 1 }) });
|
|
71
|
+
expect(await codes(root)).toEqual(["root-chant-required", "root-chant-required", "root-chant-required", "root-chant-required"]);
|
|
72
|
+
const ls = listWorkspace({ cwd: root });
|
|
73
|
+
if (!("error" in ls)) throw new Error("expected a failure");
|
|
74
|
+
expect(ls.error.message).toMatch(/pins @intentius\/chant 0\.0\.1, and this is chant .*install @intentius\/chant@0\.0\.1 at the workspace root/);
|
|
75
|
+
expect(ls.error.location).toMatchObject({ file: "chant.workspace.json" });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("the root's chant is the one installed there at the pinned version", () => {
|
|
79
|
+
const root = repo({ "chant.workspace.json": declaration([], pins("0.0.1")), ...installedChant("0.0.1") });
|
|
80
|
+
expect(rootChantBin(root, "0.0.1")).toBe(join(root, "node_modules", "@intentius", "chant", "bin", "chant"));
|
|
81
|
+
expect(rootChantBin(root, "0.0.2")).toBeUndefined();
|
|
82
|
+
expect(rootChantBin(repo({}), "0.0.1")).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("handToRootChant runs the command under the root's chant, once at most", async () => {
|
|
86
|
+
const root = repo({ "chant.workspace.json": declaration([], pins("0.0.1")), ...installedChant("0.0.1") });
|
|
87
|
+
const saved = process.env[HANDED_TO_ROOT_ENV];
|
|
88
|
+
delete process.env[HANDED_TO_ROOT_ENV];
|
|
89
|
+
try {
|
|
90
|
+
expect(await handToRootChant(root, undefined, ["workspace", "ls"])).toBe(7);
|
|
91
|
+
// Not installed at the pinned version, or this chant is the pinned one: nothing to hand to.
|
|
92
|
+
const other = repo({ "chant.workspace.json": declaration([], pins("0.0.2")), ...installedChant("0.0.1") });
|
|
93
|
+
expect(await handToRootChant(other, undefined, ["workspace", "ls"])).toBeUndefined();
|
|
94
|
+
const same = repo({ "chant.workspace.json": declaration([], pins(readerVersion())), ...installedChant("0.0.1") });
|
|
95
|
+
expect(await handToRootChant(same, undefined, ["workspace", "ls"])).toBeUndefined();
|
|
96
|
+
process.env[HANDED_TO_ROOT_ENV] = "0.0.1";
|
|
97
|
+
expect(await handToRootChant(root, undefined, ["workspace", "ls"])).toBeUndefined();
|
|
98
|
+
} finally {
|
|
99
|
+
if (saved === undefined) delete process.env[HANDED_TO_ROOT_ENV];
|
|
100
|
+
else process.env[HANDED_TO_ROOT_ENV] = saved;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test(
|
|
105
|
+
"chant workspace ls, graph, check and status hand themselves to it, with --at reading the pin at the revision",
|
|
106
|
+
() => {
|
|
107
|
+
const root = repo({ "chant.workspace.json": declaration([], pins("0.0.1")), ...installedChant("0.0.1"), ".gitignore": "node_modules\n" }, true);
|
|
108
|
+
for (const args of [["workspace", "ls", "--json"], ["workspace", "graph"], ["workspace", "check", "--json"], ["workspace", "status", "dev"], ["workspace", "ls", "--at", "HEAD"]]) {
|
|
109
|
+
const r = chant(root, ...args);
|
|
110
|
+
expect(r.status, `${args.join(" ")}: ${r.stderr}`).toBe(7);
|
|
111
|
+
expect(r.stdout.trim()).toBe(`root chant 0.0.1: ${args.join(" ")} (0.0.1)`);
|
|
112
|
+
}
|
|
113
|
+
expect(locateWorkspace(root, "HEAD").at).toMatch(/^[0-9a-f]{40}$/);
|
|
114
|
+
},
|
|
115
|
+
240_000,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a workspace read starts, and which chant does it (#2536; #2524 D15,
|
|
3
|
+
* ws-016, ws-021).
|
|
4
|
+
*
|
|
5
|
+
* {@link locateWorkspace} finds the declaration from a directory, in the
|
|
6
|
+
* working tree or in one commit's git objects (`--at <rev>`), and gives the
|
|
7
|
+
* tree every read-contract command reads from.
|
|
8
|
+
*
|
|
9
|
+
* Which chant reads the declaration: the root's. The root names its chant by
|
|
10
|
+
* pinning `@intentius/chant` in the declaration's `pins`. When it pins one,
|
|
11
|
+
* that version reads the declaration, and any other chant refuses with
|
|
12
|
+
* `root-chant-required` (`parseDeclaration`'s `rootChant` option). The CLI
|
|
13
|
+
* first hands the whole command to the pinned chant when the root has it
|
|
14
|
+
* installed ({@link handToRootChant}), so a user sees the refusal only when
|
|
15
|
+
* it isn't. When the root pins no chant, the reader's own chant reads the
|
|
16
|
+
* declaration if it meets `minReader`, and refuses with `reader-too-old`
|
|
17
|
+
* otherwise. Members are always read by their own toolchains.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { spawn } from "node:child_process";
|
|
21
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
22
|
+
import { join, relative, resolve } from "node:path";
|
|
23
|
+
import { findWorkspaceRoot } from "../project-root";
|
|
24
|
+
import { CHANT_PACKAGE, DECLARATION_FILES, findDeclarationDir, pinnedChant, readerVersion, WorkspaceReadError } from "./declaration";
|
|
25
|
+
import { parseJsonText } from "./jsonc";
|
|
26
|
+
import { findInstalledPackage } from "./kinds";
|
|
27
|
+
import { gitTop, gitTree, resolveCommit, workingTree, type WorkspaceTree } from "./tree";
|
|
28
|
+
|
|
29
|
+
export interface LocatedWorkspace {
|
|
30
|
+
/** The files, rooted at the workspace root. */
|
|
31
|
+
tree: WorkspaceTree;
|
|
32
|
+
/** The workspace root relative to the git root, `"."` for the git root itself. Absolute outside git. */
|
|
33
|
+
root: string;
|
|
34
|
+
/** The workspace root on disk, where pinned packages and toolchains are installed. Also for `--at`. */
|
|
35
|
+
rootOnDisk: string;
|
|
36
|
+
/** The full commit id for `--at`, or null for the working tree. */
|
|
37
|
+
at: string | null;
|
|
38
|
+
/** The top of the git repository, when there is one. */
|
|
39
|
+
top: string | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const posix = (p: string) => p.split("\\").join("/");
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Find the workspace whose declaration is nearest above `cwd`, in the working
|
|
46
|
+
* tree or at revision `at`. Throws a {@link WorkspaceReadError}:
|
|
47
|
+
* `declaration-missing`, or for `--at`, `not-a-git-repository` and
|
|
48
|
+
* `revision-unknown`.
|
|
49
|
+
*/
|
|
50
|
+
export function locateWorkspace(cwd: string, at?: string): LocatedWorkspace {
|
|
51
|
+
const top = gitTop(cwd);
|
|
52
|
+
if (at !== undefined) {
|
|
53
|
+
if (!top) throw new WorkspaceReadError("not-a-git-repository", "--at reads git objects, and this directory is not in a git repository");
|
|
54
|
+
const commit = resolveCommit(top, at);
|
|
55
|
+
if (!commit) throw new WorkspaceReadError("revision-unknown", `--at ${at} names no commit in this repository`);
|
|
56
|
+
const whole = gitTree(top, commit);
|
|
57
|
+
const start = posix(relative(top, realpathSync(resolve(cwd))));
|
|
58
|
+
const found = findDeclarationDir(whole, start.startsWith("..") ? "" : start);
|
|
59
|
+
if (found === undefined) {
|
|
60
|
+
throw new WorkspaceReadError("declaration-missing", `no chant.workspace.json or .jsonc between ${start || "."} and the git root at ${commit.slice(0, 8)}`);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
tree: found === "" ? whole : gitTree(top, commit, found),
|
|
64
|
+
root: found === "" ? "." : found,
|
|
65
|
+
rootOnDisk: join(top, ...found.split("/")),
|
|
66
|
+
at: commit,
|
|
67
|
+
top,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const found = findWorkspaceRoot(cwd);
|
|
71
|
+
if (!found) {
|
|
72
|
+
throw new WorkspaceReadError(
|
|
73
|
+
"declaration-missing",
|
|
74
|
+
top
|
|
75
|
+
? "no chant.workspace.json or .jsonc between this directory and the git root; chant workspace init proposes one"
|
|
76
|
+
: "no chant.workspace.json or .jsonc in this directory; chant workspace init proposes one",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
// git reports the top with symlinks resolved (/private/var on macOS), so compare like with like.
|
|
80
|
+
const rel = top ? posix(relative(top, realpathSync(found.dir))) : found.dir;
|
|
81
|
+
return { tree: workingTree(found.dir), root: rel === "" ? "." : rel, rootOnDisk: found.dir, at: null, top };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The `bin/chant` of `@intentius/chant` at `version`, installed where the
|
|
86
|
+
* workspace root resolves packages. Undefined when it isn't installed there,
|
|
87
|
+
* or is installed at another version.
|
|
88
|
+
*/
|
|
89
|
+
export function rootChantBin(rootOnDisk: string, version: string): string | undefined {
|
|
90
|
+
const dir = findInstalledPackage(CHANT_PACKAGE, rootOnDisk);
|
|
91
|
+
if (!dir) return undefined;
|
|
92
|
+
let pkg: { version?: unknown; bin?: unknown };
|
|
93
|
+
try {
|
|
94
|
+
pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8")) as typeof pkg;
|
|
95
|
+
} catch {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
if (pkg.version !== version) return undefined;
|
|
99
|
+
const rel = typeof pkg.bin === "string" ? pkg.bin : (pkg.bin as Record<string, unknown> | undefined)?.chant;
|
|
100
|
+
if (typeof rel !== "string") return undefined;
|
|
101
|
+
const bin = join(dir, rel);
|
|
102
|
+
return existsSync(bin) ? bin : undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Set in the environment of a command handed to the root's chant, so it is handed on once at most. */
|
|
106
|
+
export const HANDED_TO_ROOT_ENV = "CHANT_WORKSPACE_ROOT_CHANT";
|
|
107
|
+
|
|
108
|
+
/** The version the declaration at `located` pins for `@intentius/chant`, or undefined. Never throws. */
|
|
109
|
+
export function declaredChantPin(located: LocatedWorkspace): string | undefined {
|
|
110
|
+
const file = DECLARATION_FILES.find((f) => located.tree.stat(f) === "file");
|
|
111
|
+
if (!file) return undefined;
|
|
112
|
+
let text: string;
|
|
113
|
+
try {
|
|
114
|
+
text = located.tree.read(file);
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
const parsed = parseJsonText(text, { jsonc: file.endsWith(".jsonc") });
|
|
119
|
+
if (!parsed.ok || parsed.value === null || typeof parsed.value !== "object" || Array.isArray(parsed.value)) return undefined;
|
|
120
|
+
return pinnedChant(parsed.value as Record<string, unknown>)?.version;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Hand a read-contract command to the root's chant (ws-021). When the
|
|
125
|
+
* declaration above `cwd` (at `at`, for `--at`) pins a chant other than this
|
|
126
|
+
* one, and the root has that chant installed, run the same command line
|
|
127
|
+
* under it and return its exit code. Otherwise return undefined, and this
|
|
128
|
+
* chant runs the command: it reads the declaration itself, or refuses with
|
|
129
|
+
* `root-chant-required` when the pinned chant isn't installed.
|
|
130
|
+
*/
|
|
131
|
+
export async function handToRootChant(cwd: string, at: string | undefined, argv: string[] = process.argv.slice(2)): Promise<number | undefined> {
|
|
132
|
+
if (process.env[HANDED_TO_ROOT_ENV]) return undefined;
|
|
133
|
+
let located: LocatedWorkspace;
|
|
134
|
+
try {
|
|
135
|
+
located = locateWorkspace(cwd, at);
|
|
136
|
+
} catch {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const pinned = declaredChantPin(located);
|
|
140
|
+
if (pinned === undefined || pinned === readerVersion()) return undefined;
|
|
141
|
+
const bin = rootChantBin(located.rootOnDisk, pinned);
|
|
142
|
+
if (!bin) return undefined;
|
|
143
|
+
return new Promise((done) => {
|
|
144
|
+
const child = spawn(bin, argv, { stdio: "inherit", env: { ...process.env, [HANDED_TO_ROOT_ENV]: pinned } });
|
|
145
|
+
child.on("error", (e) => {
|
|
146
|
+
process.stderr.write(`could not start the root's chant ${pinned} at ${bin}: ${e.message}\n`);
|
|
147
|
+
done(1);
|
|
148
|
+
});
|
|
149
|
+
child.on("close", (code) => done(code ?? 1));
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://intentius.io/chant/schemas/workspace/kinds/v1/workspace-kinds.schema.json",
|
|
4
|
+
"title": "workspace-kinds.json",
|
|
5
|
+
"description": "Member kinds a plugin supplies (#2524 D3, #2535, ws-031). A package publishes this file as data at its ./workspace-kinds subpath. chant reads it with the file system and never imports the package, so reading kinds never loads a lexicon.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"schema",
|
|
9
|
+
"kinds"
|
|
10
|
+
],
|
|
11
|
+
"properties": {
|
|
12
|
+
"$schema": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "An editor hint. chant ignores it."
|
|
15
|
+
},
|
|
16
|
+
"$comment": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "A note for people. chant ignores it."
|
|
19
|
+
},
|
|
20
|
+
"schema": {
|
|
21
|
+
"const": 1,
|
|
22
|
+
"description": "The kinds format version."
|
|
23
|
+
},
|
|
24
|
+
"kinds": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"minItems": 1,
|
|
27
|
+
"items": {
|
|
28
|
+
"$ref": "#/$defs/kind"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"patternProperties": {
|
|
33
|
+
"^x-": true
|
|
34
|
+
},
|
|
35
|
+
"additionalProperties": false,
|
|
36
|
+
"$defs": {
|
|
37
|
+
"kind": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": [
|
|
40
|
+
"name",
|
|
41
|
+
"description",
|
|
42
|
+
"precedence",
|
|
43
|
+
"probe"
|
|
44
|
+
],
|
|
45
|
+
"properties": {
|
|
46
|
+
"name": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"pattern": "^[a-z][a-z0-9-]{0,39}$",
|
|
49
|
+
"description": "The kind's name, as a member's kind field names it. chant, workspace, other and examples are built in and can't be supplied."
|
|
50
|
+
},
|
|
51
|
+
"description": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"minLength": 1,
|
|
54
|
+
"description": "One line saying what the member's directory holds, for listings and error messages."
|
|
55
|
+
},
|
|
56
|
+
"precedence": {
|
|
57
|
+
"type": "integer",
|
|
58
|
+
"minimum": 1,
|
|
59
|
+
"maximum": 999,
|
|
60
|
+
"description": "When the probes of several kinds claim one directory, the highest precedence decides. Two claiming kinds with the same highest precedence are a tie, and a tie fails. The built-in chant kind has 500 and workspace has 1000."
|
|
61
|
+
},
|
|
62
|
+
"probe": {
|
|
63
|
+
"$ref": "#/$defs/probe"
|
|
64
|
+
},
|
|
65
|
+
"outputs": {
|
|
66
|
+
"type": "array",
|
|
67
|
+
"uniqueItems": true,
|
|
68
|
+
"description": "The outputs every member of this kind exposes as link targets (#2524 D6, #2539). A member entry of the kind may list more in its own outputs field. Without this field the kind exposes only what each entry lists.",
|
|
69
|
+
"items": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"minLength": 1,
|
|
72
|
+
"maxLength": 255,
|
|
73
|
+
"pattern": "^[^\\u0000-\\u001f]+$"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"patternProperties": {
|
|
78
|
+
"^x-": true
|
|
79
|
+
},
|
|
80
|
+
"additionalProperties": false
|
|
81
|
+
},
|
|
82
|
+
"probe": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"description": "What the probe checks in a member's directory. It runs no code. It passes when any of its clauses does, and it needs at least one.",
|
|
85
|
+
"minProperties": 1,
|
|
86
|
+
"properties": {
|
|
87
|
+
"anyFile": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"minItems": 1,
|
|
90
|
+
"uniqueItems": true,
|
|
91
|
+
"description": "The directory passes when a file matching one of these names sits directly in it. A * in a name matches any run of characters, so *.tf matches every Terraform file.",
|
|
92
|
+
"items": {
|
|
93
|
+
"$ref": "#/$defs/fileName"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"anyBlock": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"description": "The directory passes when a file directly in it, whose name matches one of in, has a line opening an unlabelled block named in blocks: leading space, the name, optional space, then {. No file is parsed.",
|
|
99
|
+
"required": [
|
|
100
|
+
"in",
|
|
101
|
+
"blocks"
|
|
102
|
+
],
|
|
103
|
+
"properties": {
|
|
104
|
+
"in": {
|
|
105
|
+
"type": "array",
|
|
106
|
+
"minItems": 1,
|
|
107
|
+
"uniqueItems": true,
|
|
108
|
+
"items": {
|
|
109
|
+
"$ref": "#/$defs/fileName"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"blocks": {
|
|
113
|
+
"type": "array",
|
|
114
|
+
"minItems": 1,
|
|
115
|
+
"uniqueItems": true,
|
|
116
|
+
"items": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_-]{0,63}$"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"additionalProperties": false
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"additionalProperties": false
|
|
126
|
+
},
|
|
127
|
+
"fileName": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"description": "A file name, with no directory part. A * matches any run of characters.",
|
|
130
|
+
"pattern": "^(?!\\.\\.?$)[^/\\\\]+$"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The warning a release ahead of environment- and plan-bound component gate
|
|
3
|
-
* approvals (chant #2574).
|
|
4
|
-
*
|
|
5
|
-
* A component gate approval is keyed by the component and the gate name only,
|
|
6
|
-
* so an approval given for one environment, or for one deploy, can pass the
|
|
7
|
-
* same gate later on another environment or on a promote or rollback. The
|
|
8
|
-
* next release binds each approval to the environment and the plan digest it
|
|
9
|
-
* was given for, the way Op gates bind a plan since #2300, and refuses an
|
|
10
|
-
* approval that records neither.
|
|
11
|
-
*
|
|
12
|
-
* This release only says so. When a component gate passes on an approval that
|
|
13
|
-
* records no plan, the driver prints one warning per component and gate, per
|
|
14
|
-
* process. The decision itself is unchanged.
|
|
15
|
-
*/
|
|
16
|
-
import type { GateResolutionRecord } from "../lifecycle/gate-ledger.js";
|
|
17
|
-
/** Forget which gates were warned about. For tests. */
|
|
18
|
-
export declare function resetUnboundGateApprovalWarnings(): void;
|
|
19
|
-
/**
|
|
20
|
-
* Warn once for `component`'s `gate` when the approval that just passed it in
|
|
21
|
-
* `env` records no plan. Returns whether a warning was printed.
|
|
22
|
-
*/
|
|
23
|
-
export declare function warnOnUnboundComponentApproval(component: string, gate: string, env: string, resolution: GateResolutionRecord): boolean;
|
|
24
|
-
//# sourceMappingURL=unbound-gate-approval.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"unbound-gate-approval.d.ts","sourceRoot":"","sources":["../../src/components/unbound-gate-approval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAMrE,uDAAuD;AACvD,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,oBAAoB,GAC/B,OAAO,CAcT"}
|