@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,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant workspace member-run` (#2537): the one process a toolchain runs for
|
|
3
|
+
* `chant workspace build|lint|audit|graph`.
|
|
4
|
+
*
|
|
5
|
+
* `member-commands.ts` groups the members by the chant each one resolves
|
|
6
|
+
* (#2524 D3, "one process per toolchain identity") and spawns that chant's
|
|
7
|
+
* `workspace member-run` once per group. The request arrives as JSON on
|
|
8
|
+
* stdin: the level-0 command line for each member, and the member's
|
|
9
|
+
* directory. This process runs them one after another, in-process, from
|
|
10
|
+
* inside each directory, so every member gets what `chant <verb> .` would
|
|
11
|
+
* print there. It answers on stdout with one {@link PROTOCOL_PREFIX} line per
|
|
12
|
+
* member, after a header line naming the protocol and this chant's version.
|
|
13
|
+
*
|
|
14
|
+
* A chant older than this command has no header to print. The caller sees
|
|
15
|
+
* that and falls back to one level-0 process per member.
|
|
16
|
+
*
|
|
17
|
+
* Internal: not listed in `chant --help`, and the protocol may change with
|
|
18
|
+
* the caller, which is always a chant of the same or a newer release.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { ENV_VAR } from "../env";
|
|
22
|
+
import type { CommandContext } from "../cli/registry";
|
|
23
|
+
import { readerVersion } from "./declaration";
|
|
24
|
+
|
|
25
|
+
/** Marks the lines of this protocol on stdout; anything else there is stray output. */
|
|
26
|
+
export const PROTOCOL_PREFIX = "@@chant-member-run@@ ";
|
|
27
|
+
export const MEMBER_RUN_PROTOCOL = 1;
|
|
28
|
+
|
|
29
|
+
export interface MemberRunRequest {
|
|
30
|
+
protocol: number;
|
|
31
|
+
units: MemberRunUnit[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface MemberRunUnit {
|
|
35
|
+
/** Echoed back on the answer line. */
|
|
36
|
+
id: string;
|
|
37
|
+
/** The member's directory, absolute. The command runs from inside it. */
|
|
38
|
+
dir: string;
|
|
39
|
+
/** The level-0 command line, such as `["lint", ".", "--format", "sarif"]`. */
|
|
40
|
+
argv: string[];
|
|
41
|
+
/**
|
|
42
|
+
* Directories (relative to `dir`) to leave out of discovery. Set for member
|
|
43
|
+
* `.`, whose project is the root minus every other member. Kept in the
|
|
44
|
+
* protocol for a member-run from before #2527, which applied it itself;
|
|
45
|
+
* from #2527 on, discovery reads the same set from the declaration.
|
|
46
|
+
*/
|
|
47
|
+
exclude?: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type MemberRunLine =
|
|
51
|
+
| { type: "header"; protocol: number; chant: string }
|
|
52
|
+
| { type: "result"; id: string; exitCode: number; stdout: string; stderr: string };
|
|
53
|
+
|
|
54
|
+
/** A `process.exit` inside a command, turned into a value. */
|
|
55
|
+
class ExitRequest extends Error {
|
|
56
|
+
constructor(readonly code: number) {
|
|
57
|
+
super(`process.exit(${code})`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type Write = typeof process.stdout.write;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Run `fn` with stdout, stderr and `process.exit` captured. Commands print
|
|
65
|
+
* through `console`, which writes through the stream's `write` at call time,
|
|
66
|
+
* so replacing `write` catches everything they print.
|
|
67
|
+
*/
|
|
68
|
+
export async function captureRun(fn: () => Promise<number>): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
|
69
|
+
const out: string[] = [];
|
|
70
|
+
const err: string[] = [];
|
|
71
|
+
const sink =
|
|
72
|
+
(into: string[]): Write =>
|
|
73
|
+
((chunk: string | Uint8Array, encoding?: unknown, cb?: unknown) => {
|
|
74
|
+
into.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
|
75
|
+
const done = typeof encoding === "function" ? encoding : cb;
|
|
76
|
+
if (typeof done === "function") (done as () => void)();
|
|
77
|
+
return true;
|
|
78
|
+
}) as Write;
|
|
79
|
+
const saved = { out: process.stdout.write, err: process.stderr.write, exit: process.exit };
|
|
80
|
+
process.stdout.write = sink(out);
|
|
81
|
+
process.stderr.write = sink(err);
|
|
82
|
+
process.exit = ((code?: number) => {
|
|
83
|
+
throw new ExitRequest(typeof code === "number" ? code : 0);
|
|
84
|
+
}) as typeof process.exit;
|
|
85
|
+
let exitCode: number;
|
|
86
|
+
try {
|
|
87
|
+
exitCode = await fn();
|
|
88
|
+
} catch (e) {
|
|
89
|
+
if (e instanceof ExitRequest) {
|
|
90
|
+
exitCode = e.code;
|
|
91
|
+
} else {
|
|
92
|
+
err.push(`${e instanceof Error ? e.message : String(e)}\n`);
|
|
93
|
+
exitCode = 1;
|
|
94
|
+
}
|
|
95
|
+
} finally {
|
|
96
|
+
process.stdout.write = saved.out;
|
|
97
|
+
process.stderr.write = saved.err;
|
|
98
|
+
process.exit = saved.exit;
|
|
99
|
+
}
|
|
100
|
+
return { exitCode, stdout: out.join(""), stderr: err.join("") };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function emit(line: MemberRunLine): void {
|
|
104
|
+
process.stdout.write(`${PROTOCOL_PREFIX}${JSON.stringify(line)}\n`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function readStdin(): Promise<string> {
|
|
108
|
+
const chunks: Buffer[] = [];
|
|
109
|
+
for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
|
|
110
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Run each unit of `request` in turn, answering on stdout. */
|
|
114
|
+
export async function runMemberUnits(request: MemberRunRequest, run: (argv: string[]) => Promise<number>): Promise<number> {
|
|
115
|
+
emit({ type: "header", protocol: MEMBER_RUN_PROTOCOL, chant: readerVersion() });
|
|
116
|
+
const startDir = process.cwd();
|
|
117
|
+
const startEnv = process.env[ENV_VAR];
|
|
118
|
+
for (const unit of request.units) {
|
|
119
|
+
const result = await captureRun(async () => {
|
|
120
|
+
process.chdir(unit.dir);
|
|
121
|
+
// `unit.exclude` needs no action here: discovery reads the declaration
|
|
122
|
+
// itself and leaves member `.`'s exclusions out of every walk (#2527).
|
|
123
|
+
return run(unit.argv);
|
|
124
|
+
});
|
|
125
|
+
if (startEnv === undefined) delete process.env[ENV_VAR];
|
|
126
|
+
else process.env[ENV_VAR] = startEnv;
|
|
127
|
+
process.chdir(startDir);
|
|
128
|
+
emit({ type: "result", id: unit.id, ...result });
|
|
129
|
+
}
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function runWorkspaceMemberRun(_ctx: CommandContext, run: (argv: string[]) => Promise<number>): Promise<number> {
|
|
134
|
+
let request: MemberRunRequest;
|
|
135
|
+
try {
|
|
136
|
+
request = JSON.parse(await readStdin()) as MemberRunRequest;
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error(`chant workspace member-run: the request on stdin is not JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
if (request.protocol !== MEMBER_RUN_PROTOCOL || !Array.isArray(request.units)) {
|
|
142
|
+
console.error(`chant workspace member-run: this chant speaks protocol ${MEMBER_RUN_PROTOCOL}, and the request asks for ${String(request.protocol)}`);
|
|
143
|
+
return 1;
|
|
144
|
+
}
|
|
145
|
+
return runMemberUnits(request, run);
|
|
146
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The read contract as a whole (#2536, #2524 D15): every output schema, run
|
|
3
|
+
* against the reference workspace (#2543) in this checkout.
|
|
4
|
+
*
|
|
5
|
+
* `reference-workspace/` is a nested workspace of the chant repo, with a
|
|
6
|
+
* `chant` member (`delivery`), three `other` members and decision records.
|
|
7
|
+
* Each read-contract command reads it here, and its output must validate
|
|
8
|
+
* against the command's schema: `ls`, `graph` (running delivery's real
|
|
9
|
+
* `chant graph`), `check`, `status` and `records`, in the working tree and,
|
|
10
|
+
* for `ls`, `graph` and `check`, at `HEAD` through `--at`.
|
|
11
|
+
*
|
|
12
|
+
* The schemas themselves are checked here too: each is a draft 2020-12
|
|
13
|
+
* document under `https://intentius.io/chant/schemas/workspace/<command>/v1/`,
|
|
14
|
+
* at the contract version this chant writes, naming the chant floor.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { realpathSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { pathToFileURL } from "node:url";
|
|
20
|
+
import { describe, expect, test } from "vitest";
|
|
21
|
+
import { contract, git, REPO, validSchema } from "./__fixtures__/contract-repo";
|
|
22
|
+
import checkSchema from "./check.schema.json";
|
|
23
|
+
import { workspaceGraph } from "./graph-cli";
|
|
24
|
+
import graphSchema from "./graph.schema.json";
|
|
25
|
+
import { runChecks } from "./lineage-check";
|
|
26
|
+
import { listWorkspace } from "./ls";
|
|
27
|
+
import lsSchema from "./ls.schema.json";
|
|
28
|
+
import { readerBin, type Toolchain } from "./member-commands";
|
|
29
|
+
import { READ_CONTRACT_FLOOR, READ_CONTRACT_VERSION } from "./reason-codes";
|
|
30
|
+
import { queryRecords } from "./records-cli";
|
|
31
|
+
import recordsSchema from "./records.schema.json";
|
|
32
|
+
import { workspaceStatus } from "./status";
|
|
33
|
+
import statusSchema from "./status.schema.json";
|
|
34
|
+
|
|
35
|
+
const FIXTURE = join(REPO, "reference-workspace");
|
|
36
|
+
const TIMEOUT = 240_000;
|
|
37
|
+
|
|
38
|
+
const SCHEMAS = { ls: lsSchema, graph: graphSchema, check: checkSchema, status: statusSchema, records: recordsSchema };
|
|
39
|
+
|
|
40
|
+
/** This checkout's chant, started the way the CLI starts it, for members with no toolchain of their own. */
|
|
41
|
+
const reader: Toolchain = {
|
|
42
|
+
command: [process.execPath, "--import", pathToFileURL(join(REPO, "node_modules", "tsx", "dist", "loader.mjs")).href, join(REPO, "packages", "core", "src", "cli", "main.ts")],
|
|
43
|
+
identity: realpathSync(readerBin()),
|
|
44
|
+
source: "reader",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
describe("the read contract's schemas", () => {
|
|
48
|
+
test.each(Object.entries(SCHEMAS))("%s is a valid draft 2020-12 document at the contract version, naming the floor", (name, schema) => {
|
|
49
|
+
expect(validSchema(schema)).toBe(true);
|
|
50
|
+
expect(schema.$id).toBe(`https://intentius.io/chant/schemas/workspace/${name}/v${READ_CONTRACT_VERSION}/${name}.schema.json`);
|
|
51
|
+
for (const branch of ["result", "failure"] as const) {
|
|
52
|
+
const props = (schema.$defs as Record<string, { properties: Record<string, unknown> }>)[branch].properties;
|
|
53
|
+
expect(props.contract, `${name} ${branch}`).toEqual({ const: READ_CONTRACT_VERSION });
|
|
54
|
+
expect(props.$schema, `${name} ${branch}`).toEqual({ const: schema.$id });
|
|
55
|
+
}
|
|
56
|
+
expect(schema.description).toContain(`chant ${READ_CONTRACT_FLOOR} and newer`);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("every schema against the reference workspace (#2543)", () => {
|
|
61
|
+
const head = git(REPO, "rev-parse", "HEAD");
|
|
62
|
+
|
|
63
|
+
test("ls, in the working tree and at HEAD", () => {
|
|
64
|
+
const { expectValid } = contract(lsSchema);
|
|
65
|
+
for (const at of [undefined, "HEAD"]) {
|
|
66
|
+
const doc = listWorkspace({ cwd: join(FIXTURE, "delivery"), at });
|
|
67
|
+
expectValid(doc);
|
|
68
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
69
|
+
expect(doc.workspace).toMatchObject({ name: "reference", root: "reference-workspace" });
|
|
70
|
+
expect(doc.at).toBe(at ? head : null);
|
|
71
|
+
expect(doc.members.map((m) => [m.name, m.readable])).toEqual([
|
|
72
|
+
["app", true],
|
|
73
|
+
["delivery", true],
|
|
74
|
+
["design-client", true],
|
|
75
|
+
["design", true],
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test(
|
|
81
|
+
"graph runs delivery's own chant graph, in the working tree and at HEAD",
|
|
82
|
+
async () => {
|
|
83
|
+
const { expectValid } = contract(graphSchema);
|
|
84
|
+
for (const at of [undefined, "HEAD"]) {
|
|
85
|
+
const { doc, failed } = await workspaceGraph({ cwd: FIXTURE, at, reader });
|
|
86
|
+
expectValid(doc);
|
|
87
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
88
|
+
expect(failed, JSON.stringify(doc.members)).toBe(false);
|
|
89
|
+
expect(doc.workspace).toEqual({ name: "reference", root: "reference-workspace" });
|
|
90
|
+
expect(doc.at).toBe(at ? head : null);
|
|
91
|
+
const delivery = doc.members.find((m) => m.name === "delivery");
|
|
92
|
+
expect(delivery).toMatchObject({ status: "composed", reason: null, irVersion: 1 });
|
|
93
|
+
expect(doc.groups.byMember.delivery.length).toBeGreaterThan(0);
|
|
94
|
+
expect(doc.nodes.every((n) => n.id.startsWith("delivery/") && n.member === "delivery")).toBe(true);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
TIMEOUT,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
test("check, in the working tree and at HEAD", async () => {
|
|
101
|
+
const { expectValid } = contract(checkSchema);
|
|
102
|
+
for (const at of [undefined, "HEAD"]) {
|
|
103
|
+
const doc = await runChecks(FIXTURE, at);
|
|
104
|
+
expectValid(doc);
|
|
105
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
106
|
+
expect(doc.workspace).toEqual({ name: "reference", root: "reference-workspace" });
|
|
107
|
+
expect(doc.declaration?.diagnostics.filter((d) => d.severity === "error")).toEqual([]);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("status", async () => {
|
|
112
|
+
const doc = await workspaceStatus({ cwd: FIXTURE, env: "dev" });
|
|
113
|
+
contract(statusSchema).expectValid(doc);
|
|
114
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
115
|
+
expect(doc.workspace).toMatchObject({ name: "reference", root: "reference-workspace" });
|
|
116
|
+
expect(doc.members.map((m) => m.name)).toEqual(["app", "delivery", "design-client", "design"]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("records, in the working tree and at HEAD", async () => {
|
|
120
|
+
const { expectValid } = contract(recordsSchema);
|
|
121
|
+
for (const at of [undefined, "HEAD"]) {
|
|
122
|
+
const doc = await queryRecords({ kind: "decisions/decision.kind.mjs", cwd: FIXTURE, at });
|
|
123
|
+
expectValid(doc);
|
|
124
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
125
|
+
expect(doc.summary.invalid).toBe(0);
|
|
126
|
+
expect(doc.records.map((r) => r.id)).toContain("ref-001");
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One closed list of reason codes (#2536, #2524 D15): every command's own
|
|
3
|
+
* list is a subset of `REASON_CODES`, together they cover it, the output
|
|
4
|
+
* schemas name nothing outside it, and no source file under `workspace/`
|
|
5
|
+
* emits a code outside it. The read-contract page documents every code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { describe, expect, test } from "vitest";
|
|
11
|
+
import { REPO } from "./__fixtures__/contract-repo";
|
|
12
|
+
import { MEMBER_RUN_REASON_CODES } from "./compose-graph";
|
|
13
|
+
import { WORKSPACE_ERROR_CODES } from "./declaration";
|
|
14
|
+
import { GRAPH_ERROR_CODES } from "./graph-cli";
|
|
15
|
+
import { CHECK_CODES, CHECK_ERROR_CODES } from "./lineage-check";
|
|
16
|
+
import { GROUP_REASON_CODES, MEMBER_REASON_CODES } from "./ls";
|
|
17
|
+
import { isReasonCode, REASON_CODES, REASONS } from "./reason-codes";
|
|
18
|
+
import { READ_ERROR_CODES, RECORD_REASON_CODES } from "./records";
|
|
19
|
+
import { STATUS_ERROR_CODES, STATUS_REASON_CODES } from "./status";
|
|
20
|
+
|
|
21
|
+
const HERE = import.meta.dirname;
|
|
22
|
+
|
|
23
|
+
const PER_COMMAND: Record<string, readonly string[]> = {
|
|
24
|
+
WORKSPACE_ERROR_CODES,
|
|
25
|
+
MEMBER_REASON_CODES,
|
|
26
|
+
GROUP_REASON_CODES,
|
|
27
|
+
MEMBER_RUN_REASON_CODES,
|
|
28
|
+
GRAPH_ERROR_CODES,
|
|
29
|
+
CHECK_CODES,
|
|
30
|
+
CHECK_ERROR_CODES,
|
|
31
|
+
STATUS_REASON_CODES,
|
|
32
|
+
STATUS_ERROR_CODES,
|
|
33
|
+
RECORD_REASON_CODES,
|
|
34
|
+
READ_ERROR_CODES,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Every string in an `enum` under a property named `code`, anywhere in a schema. */
|
|
38
|
+
function schemaCodes(node: unknown, underCode = false, out = new Set<string>()): Set<string> {
|
|
39
|
+
if (Array.isArray(node)) {
|
|
40
|
+
for (const n of node) schemaCodes(n, underCode, out);
|
|
41
|
+
} else if (node !== null && typeof node === "object") {
|
|
42
|
+
for (const [k, v] of Object.entries(node)) {
|
|
43
|
+
if (k === "enum" && underCode) for (const e of v as unknown[]) out.add(String(e));
|
|
44
|
+
schemaCodes(v, k === "code" || (underCode && k !== "properties"), out);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sourceFiles(dir: string): string[] {
|
|
51
|
+
const out: string[] = [];
|
|
52
|
+
for (const d of readdirSync(dir, { withFileTypes: true })) {
|
|
53
|
+
if (d.isDirectory() && d.name !== "__fixtures__") out.push(...sourceFiles(join(dir, d.name)));
|
|
54
|
+
else if (d.isFile() && d.name.endsWith(".ts") && !d.name.endsWith(".test.ts")) out.push(join(dir, d.name));
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Codes that are someone else's: zod's issue codes. */
|
|
60
|
+
const NOT_OURS = new Set(["custom"]);
|
|
61
|
+
|
|
62
|
+
describe("the closed list of reason codes", () => {
|
|
63
|
+
test("has no duplicates, and every code says what it means", () => {
|
|
64
|
+
expect(new Set(REASON_CODES).size).toBe(REASON_CODES.length);
|
|
65
|
+
for (const c of REASON_CODES) {
|
|
66
|
+
expect(c).toMatch(/^[a-z]+(-[a-z0-9]+)*$/);
|
|
67
|
+
expect(REASONS[c].length).toBeGreaterThan(10);
|
|
68
|
+
}
|
|
69
|
+
expect(isReasonCode("dir-missing")).toBe(true);
|
|
70
|
+
expect(isReasonCode("toString")).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("each command's list is a subset, and together they are the whole list", () => {
|
|
74
|
+
const union = new Set<string>();
|
|
75
|
+
for (const [name, codes] of Object.entries(PER_COMMAND)) {
|
|
76
|
+
for (const c of codes) {
|
|
77
|
+
expect(isReasonCode(c), `${name} has ${c}, which is not in reason-codes.ts`).toBe(true);
|
|
78
|
+
union.add(c);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
expect([...union].sort()).toEqual([...REASON_CODES].sort());
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("the output schemas name exactly these codes", () => {
|
|
85
|
+
const named = new Set<string>();
|
|
86
|
+
for (const f of readdirSync(HERE).filter((f) => f.endsWith(".schema.json") && !f.startsWith("declaration") && !f.startsWith("workspace-kinds"))) {
|
|
87
|
+
for (const c of schemaCodes(JSON.parse(readFileSync(join(HERE, f), "utf-8")))) {
|
|
88
|
+
expect(isReasonCode(c), `${f} names ${c}, which is not in reason-codes.ts`).toBe(true);
|
|
89
|
+
named.add(c);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
expect([...named].sort()).toEqual([...REASON_CODES].sort());
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("no source file emits a code outside the list", () => {
|
|
96
|
+
const emitted = /(?:\bcode:\s*|(?:WorkspaceReadError|RecordReadError|StatusError)\(\s*)"([a-z0-9-]+)"/g;
|
|
97
|
+
let seen = 0;
|
|
98
|
+
for (const file of sourceFiles(HERE)) {
|
|
99
|
+
const text = readFileSync(file, "utf-8");
|
|
100
|
+
for (const m of text.matchAll(emitted)) {
|
|
101
|
+
if (NOT_OURS.has(m[1])) continue;
|
|
102
|
+
seen++;
|
|
103
|
+
expect(isReasonCode(m[1]), `${file.slice(HERE.length + 1)} emits ${m[1]}, which is not in reason-codes.ts`).toBe(true);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
expect(seen).toBeGreaterThan(30);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("the read-contract page documents every code", () => {
|
|
110
|
+
const page = readFileSync(join(REPO, "docs", "src", "content", "docs", "reference", "workspace-read-contract.mdx"), "utf-8");
|
|
111
|
+
for (const c of REASON_CODES) expect(page, c).toContain(`| \`${c}\` |`);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workspace read contract's closed list of reason codes (#2536, #2524
|
|
3
|
+
* D15, ws-017, ws-020).
|
|
4
|
+
*
|
|
5
|
+
* Every code a `chant workspace` read prints is here: the error that stops a
|
|
6
|
+
* read, the reason an unreadable member, group, ledger or record is listed
|
|
7
|
+
* with, and the lineage lock findings of `check`. Each command's own list
|
|
8
|
+
* (`WORKSPACE_ERROR_CODES`, `MEMBER_REASON_CODES`, `STATUS_REASON_CODES`,
|
|
9
|
+
* `RECORD_REASON_CODES` and the rest) is a subset of this one, which the
|
|
10
|
+
* compiler checks through `satisfies`. `reason-codes.test.ts` checks that the
|
|
11
|
+
* output schemas and the source emit nothing outside it.
|
|
12
|
+
*
|
|
13
|
+
* The list is closed: a reader may switch on a code, and a new code is a
|
|
14
|
+
* contract change. Within contract version 1, before the floor release, codes
|
|
15
|
+
* may still be added; after it, a new code means a new contract version.
|
|
16
|
+
*
|
|
17
|
+
* `chant workspace check` also reports `WSP` ids. Those are the declaration
|
|
18
|
+
* check catalog (`checks.ts`), a separate closed list, and a finding whose
|
|
19
|
+
* cause is one of the codes here carries that code too.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** The version of the read contract this chant writes: the declaration format, the output schemas and these codes. */
|
|
23
|
+
export const READ_CONTRACT_VERSION = 1;
|
|
24
|
+
|
|
25
|
+
/** The first chant release that writes contract version 1. A reader that needs the contract refuses an older chant. */
|
|
26
|
+
export const READ_CONTRACT_FLOOR = "0.81.0";
|
|
27
|
+
|
|
28
|
+
/** Each code and what it means. The key order is the order the docs list them in. */
|
|
29
|
+
export const REASONS = {
|
|
30
|
+
// Reading the declaration (ls, graph, check, status).
|
|
31
|
+
"declaration-missing": "No chant.workspace.json or .jsonc between the directory and the git root.",
|
|
32
|
+
"declaration-ambiguous": "Both chant.workspace.json and chant.workspace.jsonc exist.",
|
|
33
|
+
"declaration-unparseable": "The declaration is not valid JSON, or not valid JSONC for .jsonc.",
|
|
34
|
+
"declaration-invalid": "The declaration does not match its schema, repeats a name, or --member names no entry.",
|
|
35
|
+
"placement-invalid": "A member or group match breaks a placement rule.",
|
|
36
|
+
"reader-too-old": "The declaration's minReader is newer than the chant reading it.",
|
|
37
|
+
"root-chant-required": "The declaration pins a chant version other than the reader's, and that chant is not installed at the workspace root.",
|
|
38
|
+
// --at and git.
|
|
39
|
+
"not-a-git-repository": "--at, or a ledger read, needs a git repository and there is none.",
|
|
40
|
+
"revision-unknown": "--at names no commit.",
|
|
41
|
+
// status.
|
|
42
|
+
"environment-invalid": "An environment name that can't name a ledger directory.",
|
|
43
|
+
// A member or group that can't be read (ls, graph).
|
|
44
|
+
"dir-missing": "The member's directory does not exist.",
|
|
45
|
+
"unknown-kind": "No built-in kind or pinned package supplies the member's kind.",
|
|
46
|
+
"kind-probe-failed": "The member's directory is not what its kind reads.",
|
|
47
|
+
"no-matches": "An example group matches no directory holding a chant project.",
|
|
48
|
+
// A member graph leaves out (graph, and the other per-member commands).
|
|
49
|
+
"kind-not-run": "The member's kind is one the per-member commands don't run, such as other.",
|
|
50
|
+
"command-failed": "The member's own command exited with a failure.",
|
|
51
|
+
"output-unreadable": "The member's command printed something that isn't the document asked for.",
|
|
52
|
+
"ir-version-unsupported": "The member's IR has a version this chant can't read.",
|
|
53
|
+
// A ledger status can't fully read.
|
|
54
|
+
"ledger-unreadable": "Reading the ledger failed, so nothing from it is listed.",
|
|
55
|
+
"ledger-malformed": "Some lines of the ledger aren't release records; the rest are listed.",
|
|
56
|
+
// A record that isn't valid (records).
|
|
57
|
+
"record-unparseable": "No front matter, a YAML error, or a value outside the JSON subset of YAML.",
|
|
58
|
+
"record-schema-invalid": "The front matter does not match the kind's schema.",
|
|
59
|
+
"record-id-duplicate": "Another record earlier in path order has the same id.",
|
|
60
|
+
"record-supersedes-unknown": "A supersedes link names an id no record has.",
|
|
61
|
+
"record-supersedes-conflict": "A second closed record supersedes a record another one already superseded.",
|
|
62
|
+
// A records read that fails (records).
|
|
63
|
+
"kind-unreadable": "The record kind file is missing or could not be imported.",
|
|
64
|
+
"kind-invalid": "The record kind file exports no recordKind, or its shape is wrong.",
|
|
65
|
+
"schema-unreadable": "The schema file the record kind names is missing or is not JSON.",
|
|
66
|
+
"schema-id-mismatch": "The schema's $id differs from the id the record kind names.",
|
|
67
|
+
"schema-invalid": "The record schema itself does not compile.",
|
|
68
|
+
"location-missing": "The records directory does not exist, in the tree or at the revision.",
|
|
69
|
+
// The lineage lock (check).
|
|
70
|
+
"lock-invalid": "The lineage lock can't be read.",
|
|
71
|
+
"manual-step-open": "A scope in the lineage lock has an open manual step.",
|
|
72
|
+
} as const;
|
|
73
|
+
|
|
74
|
+
export type ReasonCode = keyof typeof REASONS;
|
|
75
|
+
|
|
76
|
+
/** Every code, in the order of {@link REASONS}. */
|
|
77
|
+
export const REASON_CODES = Object.keys(REASONS) as ReasonCode[];
|
|
78
|
+
|
|
79
|
+
export function isReasonCode(value: unknown): value is ReasonCode {
|
|
80
|
+
return typeof value === "string" && Object.prototype.hasOwnProperty.call(REASONS, value);
|
|
81
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `chant workspace records --kind <path> [--current] [--at <rev>] [--
|
|
3
|
-
* the first-test slice of the records query
|
|
2
|
+
* `chant workspace records --kind <path> [--current] [--at <rev>] [--base <rev>]
|
|
3
|
+
* [--require attested] [--json]`, the first-test slice of the records query
|
|
4
|
+
* (#2546, #2536), with each record's provenance level (#2547).
|
|
4
5
|
*
|
|
5
6
|
* It reads every record the kind file locates and prints them, with reason
|
|
6
7
|
* codes for any that are invalid. An invalid record never fails the command:
|
|
@@ -11,11 +12,14 @@
|
|
|
11
12
|
* nothing is inferred (#2525 rule 1).
|
|
12
13
|
*/
|
|
13
14
|
|
|
15
|
+
import { realpathSync } from "node:fs";
|
|
14
16
|
import { relative } from "node:path";
|
|
15
17
|
import { formatError } from "../cli/format";
|
|
16
18
|
import type { CommandContext } from "../cli/registry";
|
|
17
19
|
import { gitRevisionSource, gitRoot, resolveRevision, workingTreeSource } from "./record-source";
|
|
18
20
|
import { loadRecordKind, readRecords, RecordReadError, type ReadErrorCode, type RecordEntry } from "./records";
|
|
21
|
+
import { activeAttestors, type ProvenanceLevel } from "./trust/attestor";
|
|
22
|
+
import { policyAtBase, recordProvenance, resolveBase, type BaseSource, type RecordProvenance } from "./trust/provenance";
|
|
19
23
|
|
|
20
24
|
/** The version of the `records` output this chant writes. */
|
|
21
25
|
export const RECORDS_CONTRACT_VERSION = 1;
|
|
@@ -23,16 +27,35 @@ export const RECORDS_CONTRACT_VERSION = 1;
|
|
|
23
27
|
/** `$id` of the JSON Schema for the `--json` output, shipped beside this file. */
|
|
24
28
|
export const RECORDS_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/records/v1/records.schema.json";
|
|
25
29
|
|
|
26
|
-
const USAGE = "chant workspace records --kind <kind file> [--current] [--at <rev>] [--json]";
|
|
30
|
+
const USAGE = "chant workspace records --kind <kind file> [--current] [--at <rev>] [--base <rev>] [--require attested] [--json]";
|
|
31
|
+
|
|
32
|
+
/** Exit code when the read worked and a record falls below `--require`. */
|
|
33
|
+
export const EXIT_BELOW_REQUIRED = 2;
|
|
27
34
|
|
|
28
35
|
export interface RecordsQuery {
|
|
29
36
|
kind: string;
|
|
30
37
|
current?: boolean;
|
|
31
38
|
at?: string;
|
|
39
|
+
/** The revision the trust policy is read at (#2547). Defaults to the target branch tip. */
|
|
40
|
+
base?: string;
|
|
32
41
|
/** Where `kind` is resolved from and the repository is found. */
|
|
33
42
|
cwd: string;
|
|
34
43
|
}
|
|
35
44
|
|
|
45
|
+
/** A record as the output carries it: the entry plus its provenance (#2547). */
|
|
46
|
+
export type RecordView = RecordEntry & { provenance: RecordProvenance };
|
|
47
|
+
|
|
48
|
+
/** Where provenance was judged from (#2547). */
|
|
49
|
+
export interface TrustView {
|
|
50
|
+
/** The full commit id the policy was read at, or null when there is no base. */
|
|
51
|
+
base: string | null;
|
|
52
|
+
baseFrom: BaseSource | null;
|
|
53
|
+
/** Whether a signers file exists at base. False means every record is `unattested`. */
|
|
54
|
+
active: boolean;
|
|
55
|
+
signersPath: string;
|
|
56
|
+
problems: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
36
59
|
/** The `records` output: a result, or a failure with one error code. */
|
|
37
60
|
export type RecordsDocument =
|
|
38
61
|
| {
|
|
@@ -41,17 +64,21 @@ export type RecordsDocument =
|
|
|
41
64
|
kind: { name: string; schema: string; file: string };
|
|
42
65
|
at: string | null;
|
|
43
66
|
current: boolean;
|
|
44
|
-
|
|
67
|
+
trust: TrustView;
|
|
68
|
+
records: RecordView[];
|
|
45
69
|
summary: { total: number; valid: number; invalid: number; superseded: number };
|
|
46
70
|
}
|
|
47
71
|
| { $schema: string; contract: number; error: { code: ReadErrorCode; message: string } };
|
|
48
72
|
|
|
49
73
|
/** Run the query and build the document `--json` prints. Never throws a {@link RecordReadError}. */
|
|
50
74
|
export async function queryRecords(query: RecordsQuery): Promise<RecordsDocument> {
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
// git reports its top through symlinks resolved (/var is /private/var on
|
|
76
|
+
// macOS), so the directory has to be too, or record paths leave the repository.
|
|
77
|
+
const cwd = realpathOr(query.cwd);
|
|
78
|
+
const top = gitRoot(cwd);
|
|
79
|
+
const root = top ?? cwd;
|
|
53
80
|
try {
|
|
54
|
-
const loaded = await loadRecordKind(query.kind,
|
|
81
|
+
const loaded = await loadRecordKind(query.kind, cwd);
|
|
55
82
|
let at: string | null = null;
|
|
56
83
|
let source = workingTreeSource(root);
|
|
57
84
|
if (query.at !== undefined) {
|
|
@@ -60,6 +87,16 @@ export async function queryRecords(query: RecordsQuery): Promise<RecordsDocument
|
|
|
60
87
|
source = gitRevisionSource(top, at);
|
|
61
88
|
}
|
|
62
89
|
const result = await readRecords(loaded, { root, source, current: !!query.current });
|
|
90
|
+
// Provenance, judged by the policy at base and never by the tree read (#2547).
|
|
91
|
+
const base = top ? resolveBase(top, query.base) : { commit: null, from: null };
|
|
92
|
+
const policy = top ? policyAtBase(top, base) : policyAtBase(root, base);
|
|
93
|
+
const provenance = recordProvenance({
|
|
94
|
+
repo: top,
|
|
95
|
+
policy,
|
|
96
|
+
at,
|
|
97
|
+
paths: result.records.map((r) => r.path),
|
|
98
|
+
attestors: policy.active ? await activeAttestors() : [],
|
|
99
|
+
});
|
|
63
100
|
return {
|
|
64
101
|
$schema: RECORDS_OUTPUT_SCHEMA_ID,
|
|
65
102
|
contract: RECORDS_CONTRACT_VERSION,
|
|
@@ -70,7 +107,8 @@ export async function queryRecords(query: RecordsQuery): Promise<RecordsDocument
|
|
|
70
107
|
},
|
|
71
108
|
at,
|
|
72
109
|
current: !!query.current,
|
|
73
|
-
|
|
110
|
+
trust: { base: base.commit, baseFrom: base.from, active: policy.active, signersPath: policy.signersPath, problems: policy.problems },
|
|
111
|
+
records: result.records.map((r) => ({ ...r, provenance: provenance.get(r.path)! })),
|
|
74
112
|
summary: result.summary,
|
|
75
113
|
};
|
|
76
114
|
} catch (err) {
|
|
@@ -85,7 +123,11 @@ export async function runWorkspaceRecords(ctx: CommandContext): Promise<number>
|
|
|
85
123
|
console.error(formatError({ message: "--kind <kind file> is required", hint: USAGE }));
|
|
86
124
|
return 1;
|
|
87
125
|
}
|
|
88
|
-
|
|
126
|
+
if (args.require !== undefined && args.require !== "attested") {
|
|
127
|
+
console.error(formatError({ message: `--require takes one level, attested, not ${JSON.stringify(args.require)}`, hint: USAGE }));
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
const doc = await queryRecords({ kind: args.kind, current: args.current, at: args.at, base: args.base, cwd: process.cwd() });
|
|
89
131
|
if (args.json) {
|
|
90
132
|
console.log(JSON.stringify(doc, null, 2));
|
|
91
133
|
} else if ("error" in doc) {
|
|
@@ -93,10 +135,39 @@ export async function runWorkspaceRecords(ctx: CommandContext): Promise<number>
|
|
|
93
135
|
} else {
|
|
94
136
|
console.log(formatRecords(doc.records, doc.summary, doc.at));
|
|
95
137
|
}
|
|
96
|
-
|
|
138
|
+
if ("error" in doc) return 1;
|
|
139
|
+
if (args.require) {
|
|
140
|
+
const below = belowRequired(doc.records, args.require);
|
|
141
|
+
if (below.length > 0) {
|
|
142
|
+
console.error(
|
|
143
|
+
formatError({
|
|
144
|
+
message: `${below.length} of ${doc.records.length} records are not ${args.require}: ${below
|
|
145
|
+
.slice(0, 5)
|
|
146
|
+
.map((r) => `${r.path} (${r.provenance.level})`)
|
|
147
|
+
.join(", ")}${below.length > 5 ? ", ..." : ""}`,
|
|
148
|
+
hint: doc.trust.active ? "run with --json to see each record's reason" : `there is no signers file (${doc.trust.signersPath}) at base`,
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
return EXIT_BELOW_REQUIRED;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function realpathOr(dir: string): string {
|
|
158
|
+
try {
|
|
159
|
+
return realpathSync(dir);
|
|
160
|
+
} catch {
|
|
161
|
+
return dir;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Records whose provenance falls below `required`. Only `attested` can be required. */
|
|
166
|
+
export function belowRequired(records: RecordView[], required: ProvenanceLevel): RecordView[] {
|
|
167
|
+
return records.filter((r) => r.provenance.level !== required);
|
|
97
168
|
}
|
|
98
169
|
|
|
99
|
-
function formatRecords(records:
|
|
170
|
+
function formatRecords(records: RecordView[], summary: { total: number; valid: number; invalid: number; superseded: number }, at: string | null): string {
|
|
100
171
|
const lines: string[] = [];
|
|
101
172
|
const idWidth = Math.max(2, ...records.map((r) => (r.id ?? "-").length));
|
|
102
173
|
const stateWidth = Math.max(5, ...records.map((r) => (r.state ?? "-").length));
|
|
@@ -104,7 +175,8 @@ function formatRecords(records: RecordEntry[], summary: { total: number; valid:
|
|
|
104
175
|
const title = typeof r.data?.title === "string" ? r.data.title : r.path;
|
|
105
176
|
const flag = r.valid ? "" : " INVALID";
|
|
106
177
|
const superseded = r.supersededBy ? ` superseded by ${r.supersededBy}` : "";
|
|
107
|
-
|
|
178
|
+
const attested = r.provenance.level === "attested" ? ` attested by ${r.provenance.principal}` : "";
|
|
179
|
+
lines.push(`${(r.id ?? "-").padEnd(idWidth)} ${(r.state ?? "-").padEnd(stateWidth)} ${title}${superseded}${attested}${flag}`);
|
|
108
180
|
for (const reason of r.reasons) lines.push(`${" ".repeat(idWidth + 2)}${reason.code}: ${reason.message} (${r.path})`);
|
|
109
181
|
}
|
|
110
182
|
lines.push(
|
|
@@ -113,13 +185,13 @@ function formatRecords(records: RecordEntry[], summary: { total: number; valid:
|
|
|
113
185
|
return lines.join("\n");
|
|
114
186
|
}
|
|
115
187
|
|
|
116
|
-
/** `chant workspace <anything else
|
|
188
|
+
/** `chant workspace <anything else>`. */
|
|
117
189
|
export async function runWorkspaceUnknown(ctx: CommandContext): Promise<number> {
|
|
118
190
|
const sub = ctx.args.path && ctx.args.path !== "." ? ctx.args.path : "";
|
|
119
191
|
console.error(
|
|
120
192
|
formatError({
|
|
121
193
|
message: sub ? `Unknown workspace subcommand: ${sub}` : "chant workspace needs a subcommand",
|
|
122
|
-
hint: `
|
|
194
|
+
hint: `Workspace subcommands: audit, build, check, graph, init, lineage, lint, ls, records, status, upgrade, verify. Run "chant --help" for their options.`,
|
|
123
195
|
}),
|
|
124
196
|
);
|
|
125
197
|
return 1;
|