@intentius/chant 0.100.0 → 0.102.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/agents/checks.d.ts.map +1 -1
- package/dist/attrref.d.ts.map +1 -1
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/nginx.d.ts.map +1 -1
- package/dist/audit/prior-art.d.ts +48 -0
- package/dist/audit/prior-art.d.ts.map +1 -1
- package/dist/behaviour-kinds.d.ts.map +1 -1
- package/dist/build.d.ts +6 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/cli/build-options.d.ts +2 -0
- package/dist/cli/build-options.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/carve-emit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +16 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/import.d.ts +8 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +9 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
- package/dist/cli/commands/pinned-upgrade.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/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +9 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/lsp/server.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/build.d.ts.map +1 -1
- package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
- package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
- package/dist/cli/mcp/tools/import.d.ts.map +1 -1
- package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
- package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
- package/dist/cli/mcp/tools/search.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +9 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +33 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/codegen/naming.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts +67 -3
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/verbs/process-runner.d.ts.map +1 -1
- package/dist/components/verbs/r2-sync.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts.map +1 -1
- package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
- package/dist/components/verbs/wrangler.d.ts.map +1 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +30 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/declarable.d.ts +16 -0
- package/dist/declarable.d.ts.map +1 -1
- package/dist/discovery/cache.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -5
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +16 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.d.ts +37 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +3 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-layout.d.ts.map +1 -1
- package/dist/import/base-parser.d.ts.map +1 -1
- package/dist/import/embedded.d.ts +7 -0
- package/dist/import/embedded.d.ts.map +1 -1
- package/dist/import/parser-options.d.ts +14 -0
- package/dist/import/parser-options.d.ts.map +1 -0
- package/dist/import/parser.d.ts +17 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts.map +1 -1
- package/dist/lexicon-output.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +3 -3
- package/dist/lexicon.d.ts +78 -3
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +15 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/plan-digest.d.ts +26 -5
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lifecycle/work-lease.d.ts +61 -2
- package/dist/lifecycle/work-lease.d.ts.map +1 -1
- package/dist/lint/config.d.ts +4 -4
- package/dist/lint/engine.d.ts +6 -0
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +12 -11
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/apply.d.ts +79 -9
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/decide.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +1 -1
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +2 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +1 -1
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/apply-op.d.ts +12 -5
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts.map +1 -1
- package/dist/op/decide-read-inputs.d.ts.map +1 -1
- package/dist/op/emulator-lifecycle.d.ts +21 -2
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/gate-approval.d.ts +5 -1
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-wait.d.ts.map +1 -1
- package/dist/op/gate.d.ts +19 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +3 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +6 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/run-context.d.ts +39 -0
- package/dist/op/run-context.d.ts.map +1 -0
- package/dist/op/run-live.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts.map +1 -1
- package/dist/op/steward-beside.d.ts.map +1 -1
- package/dist/op/steward-points.d.ts.map +1 -1
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/types.d.ts +8 -3
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts +24 -4
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/project-root.d.ts.map +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/pseudo-parameter.d.ts.map +1 -1
- package/dist/reconcile.d.ts.map +1 -1
- package/dist/secret-materialization.d.ts.map +1 -1
- package/dist/serializer.d.ts +8 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/telemetry-attribution.d.ts +77 -0
- package/dist/telemetry-attribution.d.ts.map +1 -0
- package/dist/terraform/emit-source.d.ts.map +1 -1
- package/dist/terraform/parse.d.ts.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
- package/dist/workspace/agent-cli.d.ts +88 -0
- package/dist/workspace/agent-cli.d.ts.map +1 -0
- package/dist/workspace/box-factory.d.ts +73 -0
- package/dist/workspace/box-factory.d.ts.map +1 -0
- package/dist/workspace/changes-cli.d.ts.map +1 -1
- package/dist/workspace/changes.d.ts +8 -1
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
- package/dist/workspace/checks/boxes.d.ts.map +1 -1
- package/dist/workspace/checks/links.d.ts +1 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/live.d.ts +40 -0
- package/dist/workspace/checks/live.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +26 -2
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +73 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts +67 -6
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +12 -3
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +368 -0
- package/dist/workspace/conformance/writer.d.ts.map +1 -0
- package/dist/workspace/decide.d.ts +1 -1
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/declaration.d.ts +119 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +368 -4
- package/dist/workspace/export-cli.d.ts +12 -0
- package/dist/workspace/export-cli.d.ts.map +1 -0
- package/dist/workspace/export.d.ts +145 -0
- package/dist/workspace/export.d.ts.map +1 -0
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/identity.d.ts +111 -0
- package/dist/workspace/identity.d.ts.map +1 -0
- package/dist/workspace/import.d.ts +73 -0
- package/dist/workspace/import.d.ts.map +1 -0
- package/dist/workspace/intent-cli.d.ts +7 -4
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +8 -1
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +149 -5
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/kinds.d.ts +24 -6
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lineage-adopt-cli.d.ts +15 -0
- package/dist/workspace/lineage-adopt-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-adopt.d.ts +106 -0
- package/dist/workspace/lineage-adopt.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +9 -2
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-cli.d.ts +6 -1
- package/dist/workspace/lineage-cli.d.ts.map +1 -1
- package/dist/workspace/lineage-hash-index.d.ts +110 -0
- package/dist/workspace/lineage-hash-index.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +10 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -1
- package/dist/workspace/lineage-lock.d.ts +149 -2
- package/dist/workspace/lineage-lock.d.ts.map +1 -1
- package/dist/workspace/lineage-migrations.d.ts +15 -3
- package/dist/workspace/lineage-migrations.d.ts.map +1 -1
- package/dist/workspace/lineage-provenance.d.ts +18 -0
- package/dist/workspace/lineage-provenance.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +2 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +30 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-versions.d.ts +116 -0
- package/dist/workspace/lineage-versions.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +31 -5
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/ls-generated.d.ts +37 -0
- package/dist/workspace/ls-generated.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +4 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/nested-graph.d.ts +56 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -0
- package/dist/workspace/nesting.d.ts +21 -0
- package/dist/workspace/nesting.d.ts.map +1 -0
- package/dist/workspace/pin-cli.d.ts +10 -0
- package/dist/workspace/pin-cli.d.ts.map +1 -0
- package/dist/workspace/pin-integrity.d.ts +51 -0
- package/dist/workspace/pin-integrity.d.ts.map +1 -0
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/principal-classes.d.ts +114 -0
- package/dist/workspace/principal-classes.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +47 -3
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +7 -11
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-checkout.d.ts +54 -0
- package/dist/workspace/records-checkout.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +57 -2
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +5 -2
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-since.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +23 -4
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +58 -5
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/returns.d.ts +129 -0
- package/dist/workspace/returns.d.ts.map +1 -0
- package/dist/workspace/runs-cli.d.ts +164 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -0
- package/dist/workspace/runs.d.ts +423 -0
- package/dist/workspace/runs.d.ts.map +1 -0
- package/dist/workspace/source-block.d.ts +1 -1
- package/dist/workspace/source-block.d.ts.map +1 -1
- package/dist/workspace/status-gates.d.ts +15 -3
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +10 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +52 -0
- package/dist/workspace/symbols.d.ts.map +1 -0
- package/dist/workspace/template-manifest.d.ts +11 -3
- package/dist/workspace/template-manifest.d.ts.map +1 -1
- package/dist/workspace/trailer-joins.d.ts +46 -0
- package/dist/workspace/trailer-joins.d.ts.map +1 -0
- package/dist/workspace/trailers.d.ts +89 -0
- package/dist/workspace/trailers.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts +8 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -1
- package/dist/workspace/trust/dsse.d.ts +58 -0
- package/dist/workspace/trust/dsse.d.ts.map +1 -0
- package/dist/workspace/trust/evidence-cli.d.ts +66 -0
- package/dist/workspace/trust/evidence-cli.d.ts.map +1 -0
- package/dist/workspace/trust/evidence.d.ts +93 -0
- package/dist/workspace/trust/evidence.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +54 -1
- package/dist/workspace/trust/policy.d.ts.map +1 -1
- package/dist/workspace/trust/provenance.d.ts +21 -1
- package/dist/workspace/trust/provenance.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.d.ts +132 -0
- package/dist/workspace/trust/rotation.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts +33 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/signers-cli.d.ts +49 -0
- package/dist/workspace/trust/signers-cli.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +12 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
- package/dist/workspace/trust/test-repo.d.ts +13 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -1
- package/dist/workspace/trust/verify.d.ts +15 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +39 -3
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work.d.ts +26 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/dist/workspace/workspace-principals.schema.json +60 -0
- package/dist/workspace/write-scope.d.ts +259 -0
- package/dist/workspace/write-scope.d.ts.map +1 -0
- package/dist/yaml.d.ts +21 -39
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/audit/prior-art.ts +15 -0
- package/src/build.ts +8 -0
- package/src/cli/build-options.ts +4 -1
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/carve-emit-state.test.ts +4 -0
- package/src/cli/commands/carve-emit.ts +5 -19
- package/src/cli/commands/check-lexicon.test.ts +62 -1
- package/src/cli/commands/check-lexicon.ts +36 -6
- package/src/cli/commands/import-live.test.ts +87 -1
- package/src/cli/commands/import-parser-options.test.ts +133 -0
- package/src/cli/commands/import.ts +69 -25
- package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
- package/src/cli/commands/init.test.ts +8 -1
- package/src/cli/commands/init.ts +23 -8
- package/src/cli/commands/lint.test.ts +37 -0
- package/src/cli/commands/onboard.test.ts +53 -3
- package/src/cli/commands/onboard.ts +52 -4
- package/src/cli/commands/pinned-upgrade.ts +73 -33
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
- package/src/cli/handlers/components.ts +2 -2
- package/src/cli/handlers/dev.ts +8 -6
- package/src/cli/handlers/fan-out.test.ts +6 -6
- package/src/cli/handlers/fan-out.ts +2 -1
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +22 -0
- package/src/cli/handlers/lifecycle.test.ts +67 -0
- package/src/cli/handlers/lifecycle.ts +6 -5
- package/src/cli/handlers/misc.ts +2 -0
- package/src/cli/handlers/operator.ts +88 -4
- package/src/cli/handlers/run.test.ts +111 -0
- package/src/cli/handlers/run.ts +85 -24
- package/src/cli/handlers/scenario.ts +2 -2
- package/src/cli/main.test.ts +33 -0
- package/src/cli/main.ts +168 -17
- package/src/cli/mcp/workspace-tools.test.ts +5 -2
- package/src/cli/mcp/workspace-tools.ts +62 -4
- package/src/cli/registry.ts +33 -2
- package/src/cli/serve-mcp-workspace.test.ts +1 -1
- package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
- package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
- package/src/codegen/pinned-upgrade.test.ts +104 -0
- package/src/codegen/pinned-upgrade.ts +250 -56
- package/src/codegen/release-wiring.test.ts +5 -1
- package/src/codegen/testdata/postgres-tags.json +242 -0
- package/src/components/fan-out-output.test.ts +1 -1
- package/src/components/fan-out.test.ts +1 -1
- package/src/components/promote.test.ts +1 -1
- package/src/config.test.ts +21 -0
- package/src/config.ts +27 -0
- package/src/content-digest.test.ts +2 -2
- package/src/declarable.test.ts +21 -0
- package/src/declarable.ts +19 -0
- package/src/discovery/fold-composite.test.ts +41 -5
- package/src/discovery/fold-import.test.ts +129 -0
- package/src/discovery/fold-import.ts +643 -131
- package/src/discovery/fold-package-composite.test.ts +264 -0
- package/src/discovery/fold-specifiers.test.ts +191 -0
- package/src/discovery/param-deps.test.ts +66 -1
- package/src/discovery/param-deps.ts +45 -0
- package/src/discovery/specifier-resolve.ts +344 -0
- package/src/fold/fold.test.ts +39 -1
- package/src/fold/fold.ts +83 -9
- package/src/fold-provenance.test.ts +140 -0
- package/src/graph-ir.test.ts +83 -0
- package/src/graph-ir.ts +29 -0
- package/src/import/embedded.test.ts +20 -0
- package/src/import/embedded.ts +13 -0
- package/src/import/parser-options.ts +46 -0
- package/src/import/parser.ts +20 -0
- package/src/lexicon.ts +79 -3
- package/src/lifecycle/gate-ledger.test.ts +14 -0
- package/src/lifecycle/gate-ledger.ts +18 -1
- package/src/lifecycle/observe.test.ts +18 -0
- package/src/lifecycle/observe.ts +6 -4
- package/src/lifecycle/plan-digest.test.ts +54 -3
- package/src/lifecycle/plan-digest.ts +38 -8
- package/src/lifecycle/snapshot.ts +2 -2
- package/src/lifecycle/work-lease.test.ts +1 -1
- package/src/lifecycle/work-lease.ts +93 -2
- package/src/lint/engine.test.ts +55 -0
- package/src/lint/engine.ts +30 -4
- package/src/lint/output-docs.test.ts +59 -56
- package/src/op/activities/activity-contracts.ts +1 -0
- package/src/op/activities/apply.test.ts +189 -5
- package/src/op/activities/apply.ts +230 -8
- package/src/op/activities/index.ts +3 -0
- package/src/op/activities/lexicon-upgrade.test.ts +160 -6
- package/src/op/activities/lexicon-upgrade.ts +154 -51
- package/src/op/activities/propose-upgrade.ts +8 -5
- package/src/op/composites/apply-op.ts +12 -5
- package/src/op/composites/composites.test.ts +30 -3
- package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
- package/src/op/composites/lexicon-upgrade-op.ts +29 -8
- package/src/op/emulator-lifecycle.test.ts +23 -0
- package/src/op/emulator-lifecycle.ts +52 -14
- package/src/op/gate-approval.test.ts +91 -0
- package/src/op/gate-approval.ts +5 -1
- package/src/op/gate.ts +34 -8
- package/src/op/index.ts +3 -1
- package/src/op/local-executor.test.ts +15 -0
- package/src/op/local-executor.ts +40 -12
- package/src/op/local-output.ts +1 -1
- package/src/op/operator.ts +4 -0
- package/src/op/run-context.test.ts +141 -0
- package/src/op/run-context.ts +51 -0
- package/src/op/runtimes/local.test.ts +14 -0
- package/src/op/runtimes/local.ts +2 -0
- package/src/op/steward-beside.ts +2 -0
- package/src/op/types.ts +8 -3
- package/src/op/work-lease-run.test.ts +47 -0
- package/src/op/work-lease-run.ts +73 -11
- package/src/provenance.ts +35 -0
- package/src/serializer.ts +9 -0
- package/src/telemetry-attribution.test.ts +91 -0
- package/src/telemetry-attribution.ts +145 -0
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
- package/src/workspace/agent-cli.ts +138 -0
- package/src/workspace/agent.schema.json +378 -0
- package/src/workspace/behold-kinds.test.ts +1 -1
- package/src/workspace/box-factory.test.ts +320 -0
- package/src/workspace/box-factory.ts +103 -0
- package/src/workspace/changes-cli.ts +5 -0
- package/src/workspace/changes.schema.json +171 -1
- package/src/workspace/changes.ts +61 -4
- package/src/workspace/check-live.test.ts +192 -0
- package/src/workspace/check.schema.json +64 -0
- package/src/workspace/checks/boxes.test.ts +2 -0
- package/src/workspace/checks/links.ts +23 -2
- package/src/workspace/checks/live.ts +113 -0
- package/src/workspace/checks.test.ts +2 -2
- package/src/workspace/checks.ts +49 -8
- package/src/workspace/compose-graph.test.ts +47 -0
- package/src/workspace/compose-graph.ts +127 -3
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +7 -1
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +5 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
- package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
- package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
- package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
- package/src/workspace/conformance/index.mjs +30 -3
- package/src/workspace/conformance/index.ts +103 -15
- package/src/workspace/conformance/vitest.ts +44 -2
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
- package/src/workspace/conformance/writer.test.ts +156 -0
- package/src/workspace/conformance/writer.ts +880 -0
- package/src/workspace/decide.ts +7 -1
- package/src/workspace/declaration.schema.json +368 -4
- package/src/workspace/declaration.ts +270 -1
- package/src/workspace/declared-kinds.test.ts +33 -0
- package/src/workspace/evidence.schema.json +279 -0
- package/src/workspace/export-cli.ts +180 -0
- package/src/workspace/export-import.test.ts +282 -0
- package/src/workspace/export.ts +486 -0
- package/src/workspace/graph-cli.ts +51 -2
- package/src/workspace/graph-contract.test.ts +89 -4
- package/src/workspace/graph.schema.json +453 -1
- package/src/workspace/identity.test.ts +196 -0
- package/src/workspace/identity.ts +186 -0
- package/src/workspace/import.ts +325 -0
- package/src/workspace/intent-cli.ts +64 -10
- package/src/workspace/intent-record.schema.json +208 -0
- package/src/workspace/intent-record.ts +35 -1
- package/src/workspace/intent-why.test.ts +274 -0
- package/src/workspace/intent.schema.json +718 -7
- package/src/workspace/intent.test.ts +11 -2
- package/src/workspace/intent.ts +495 -32
- package/src/workspace/kinds.test.ts +5 -5
- package/src/workspace/kinds.ts +62 -33
- package/src/workspace/lineage-adopt-cli.ts +103 -0
- package/src/workspace/lineage-adopt.test.ts +552 -0
- package/src/workspace/lineage-adopt.ts +452 -0
- package/src/workspace/lineage-check.ts +26 -5
- package/src/workspace/lineage-cli.ts +12 -2
- package/src/workspace/lineage-hash-index.ts +305 -0
- package/src/workspace/lineage-init.test.ts +9 -0
- package/src/workspace/lineage-init.ts +29 -9
- package/src/workspace/lineage-lock.ts +54 -0
- package/src/workspace/lineage-migrations.test.ts +27 -0
- package/src/workspace/lineage-migrations.ts +44 -14
- package/src/workspace/lineage-provenance.ts +40 -0
- package/src/workspace/lineage-upgrade-cli.ts +9 -5
- package/src/workspace/lineage-upgrade.test.ts +92 -1
- package/src/workspace/lineage-upgrade.ts +111 -16
- package/src/workspace/lineage-versions.ts +348 -0
- package/src/workspace/links.test.ts +121 -2
- package/src/workspace/links.ts +97 -6
- package/src/workspace/ls-contract.test.ts +84 -1
- package/src/workspace/ls-generated.ts +111 -0
- package/src/workspace/ls.schema.json +19 -0
- package/src/workspace/ls.ts +8 -1
- package/src/workspace/member-commands.ts +3 -1
- package/src/workspace/nested-graph.test.ts +176 -0
- package/src/workspace/nested-graph.ts +169 -0
- package/src/workspace/nesting.ts +37 -0
- package/src/workspace/pin-cli.test.ts +71 -0
- package/src/workspace/pin-cli.ts +57 -0
- package/src/workspace/pin-integrity.test.ts +121 -0
- package/src/workspace/pin-integrity.ts +104 -0
- package/src/workspace/points-write.schema.json +2 -0
- package/src/workspace/principal-classes.test.ts +175 -0
- package/src/workspace/principal-classes.ts +307 -0
- package/src/workspace/read-contract.test.ts +24 -0
- package/src/workspace/reason-codes.test.ts +20 -2
- package/src/workspace/reason-codes.ts +53 -3
- package/src/workspace/record-sessions.ts +12 -14
- package/src/workspace/records-amend.schema.json +6 -0
- package/src/workspace/records-checkout.ts +106 -0
- package/src/workspace/records-cli.ts +195 -22
- package/src/workspace/records-close.schema.json +7 -2
- package/src/workspace/records-close.ts +10 -2
- package/src/workspace/records-formats.test.ts +13 -5
- package/src/workspace/records-new.schema.json +6 -0
- package/src/workspace/records-review.schema.json +6 -0
- package/src/workspace/records-sessions.test.ts +23 -6
- package/src/workspace/records-uncommitted.test.ts +171 -0
- package/src/workspace/records-write.test.ts +65 -8
- package/src/workspace/records-write.ts +92 -6
- package/src/workspace/records.schema.json +127 -4
- package/src/workspace/records.ts +123 -5
- package/src/workspace/returns.ts +328 -0
- package/src/workspace/runs-cli.ts +381 -0
- package/src/workspace/runs-write.schema.json +583 -0
- package/src/workspace/runs.schema.json +810 -0
- package/src/workspace/runs.test.ts +242 -0
- package/src/workspace/runs.ts +709 -0
- package/src/workspace/signers.schema.json +206 -0
- package/src/workspace/status-contract.test.ts +5 -0
- package/src/workspace/status-gates.ts +24 -9
- package/src/workspace/status.schema.json +127 -2
- package/src/workspace/status.ts +21 -1
- package/src/workspace/symbols.ts +180 -0
- package/src/workspace/template-manifest.ts +22 -4
- package/src/workspace/trailer-joins.test.ts +171 -0
- package/src/workspace/trailer-joins.ts +82 -0
- package/src/workspace/trailers.ts +148 -0
- package/src/workspace/trust/attestor.ts +15 -0
- package/src/workspace/trust/dsse.ts +134 -0
- package/src/workspace/trust/evidence-cli.ts +195 -0
- package/src/workspace/trust/evidence.test.ts +241 -0
- package/src/workspace/trust/evidence.ts +207 -0
- package/src/workspace/trust/policy.ts +110 -3
- package/src/workspace/trust/provenance.ts +41 -4
- package/src/workspace/trust/record-seal.test.ts +1 -1
- package/src/workspace/trust/rotation.test.ts +258 -0
- package/src/workspace/trust/rotation.ts +336 -0
- package/src/workspace/trust/seal.ts +63 -1
- package/src/workspace/trust/signers-cli.ts +178 -0
- package/src/workspace/trust/ssh-commit.ts +53 -4
- package/src/workspace/trust/test-repo.ts +18 -0
- package/src/workspace/trust/trust.test.ts +8 -2
- package/src/workspace/trust/verify-cli.ts +1 -0
- package/src/workspace/trust/verify.ts +22 -0
- package/src/workspace/work-cli.e2e.test.ts +28 -1
- package/src/workspace/work-cli.ts +133 -8
- package/src/workspace/work-evidence.schema.json +6 -0
- package/src/workspace/work-history.schema.json +73 -1
- package/src/workspace/work-history.test.ts +48 -5
- package/src/workspace/work-kind-fields.test.ts +154 -0
- package/src/workspace/work-lease.schema.json +14 -1
- package/src/workspace/work.ts +82 -0
- package/src/workspace/workspace-principals.schema.json +60 -0
- package/src/workspace/write-scope.test.ts +466 -0
- package/src/workspace/write-scope.ts +573 -0
- package/src/yaml.test.ts +111 -1
- package/src/yaml.ts +141 -549
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The outer workspace never writes inside a nested one (#2524 D2, ws-007,
|
|
3
|
+
* #2551). A member of kind `workspace` upgrades itself with its own
|
|
4
|
+
* `chant workspace upgrade`; the outer side only reads it
|
|
5
|
+
* (./nested-graph.ts).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readDeclaration, WorkspaceReadError } from "./declaration";
|
|
9
|
+
import { workingTree } from "./tree";
|
|
10
|
+
|
|
11
|
+
/** The nested workspaces `dir` declares, as `{ name, dir }` with `dir` relative to `dir`. Empty without a readable declaration. */
|
|
12
|
+
export function nestedWorkspaces(dir: string): { name: string; dir: string }[] {
|
|
13
|
+
try {
|
|
14
|
+
return readDeclaration(workingTree(dir))
|
|
15
|
+
.members.filter((m) => m.kind === "workspace")
|
|
16
|
+
.map((m) => ({ name: m.name, dir: m.dir }));
|
|
17
|
+
} catch (err) {
|
|
18
|
+
if (err instanceof WorkspaceReadError) return [];
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The paths, of `paths` (relative to the repository root), that lie inside a
|
|
25
|
+
* nested workspace of the workspace at `dir`, whose own path from the
|
|
26
|
+
* repository root is `rel` ("" for the root).
|
|
27
|
+
*/
|
|
28
|
+
export function pathsInsideNested(dir: string, rel: string, paths: readonly string[]): { member: string; path: string }[] {
|
|
29
|
+
const nested = nestedWorkspaces(dir).map((n) => ({ member: n.name, prefix: `${[rel, n.dir].filter(Boolean).join("/")}/` }));
|
|
30
|
+
if (nested.length === 0) return [];
|
|
31
|
+
const out: { member: string; path: string }[] = [];
|
|
32
|
+
for (const path of paths) {
|
|
33
|
+
const hit = nested.find((n) => path.startsWith(n.prefix));
|
|
34
|
+
if (hit) out.push({ member: hit.member, path });
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
5
|
+
import type { CommandContext } from "../cli/registry";
|
|
6
|
+
import { runWorkspacePin } from "./pin-cli";
|
|
7
|
+
import { integrityOf } from "./pin-integrity";
|
|
8
|
+
|
|
9
|
+
const cwd = process.cwd();
|
|
10
|
+
const scratch: string[] = [];
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
process.chdir(cwd);
|
|
13
|
+
vi.restoreAllMocks();
|
|
14
|
+
for (const d of scratch.splice(0)) rmSync(d, { recursive: true, force: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function workspace(integrity?: string): string {
|
|
18
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-pin-cli-")));
|
|
19
|
+
scratch.push(root);
|
|
20
|
+
const files: Record<string, string> = {
|
|
21
|
+
"plugins/tf/package.json": JSON.stringify({ name: "tf" }),
|
|
22
|
+
"plugins/tf/k.json": "{}",
|
|
23
|
+
"chant.workspace.json": JSON.stringify({ name: "w", schema: 1, pins: [{ path: "plugins/tf", ...(integrity ? { integrity } : {}) }], members: [{ name: "w", dir: ".", kind: "other", because: "test" }] }),
|
|
24
|
+
};
|
|
25
|
+
for (const [p, t] of Object.entries(files)) {
|
|
26
|
+
mkdirSync(dirname(join(root, p)), { recursive: true });
|
|
27
|
+
writeFileSync(join(root, p), t);
|
|
28
|
+
}
|
|
29
|
+
return root;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function pin(root: string, path: string | undefined, json = false) {
|
|
33
|
+
process.chdir(root);
|
|
34
|
+
const out: string[] = [];
|
|
35
|
+
const err: string[] = [];
|
|
36
|
+
vi.spyOn(console, "log").mockImplementation((s: string) => void out.push(s));
|
|
37
|
+
vi.spyOn(console, "error").mockImplementation((s: string) => void err.push(s));
|
|
38
|
+
const code = await runWorkspacePin({ args: { extraPositional: path, json } } as unknown as CommandContext);
|
|
39
|
+
return { code, out: out.join("\n"), err: err.join("\n") };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("chant workspace pin (#2547)", () => {
|
|
43
|
+
test("prints the integrity value, and says an unpinned path pin needs it", async () => {
|
|
44
|
+
const root = workspace();
|
|
45
|
+
const r = await pin(root, "plugins/tf");
|
|
46
|
+
expect(r.code).toBe(0);
|
|
47
|
+
expect(r.out).toBe(integrityOf(join(root, "plugins/tf")).integrity);
|
|
48
|
+
expect(r.err).toContain('is a path pin with no integrity; add "integrity"');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("--json names the path, the value, the file count and whether the declared pin matches", async () => {
|
|
52
|
+
const root = workspace();
|
|
53
|
+
const { integrity } = integrityOf(join(root, "plugins/tf"));
|
|
54
|
+
const matching = workspace(integrity);
|
|
55
|
+
const r = await pin(matching, "plugins/tf", true);
|
|
56
|
+
expect(JSON.parse(r.out)).toEqual({ path: "plugins/tf", integrity: integrityOf(join(matching, "plugins/tf")).integrity, files: 2, pin: "matches" });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("exits 1 when the declared pin no longer matches", async () => {
|
|
60
|
+
const root = workspace("sha256-AAAA");
|
|
61
|
+
const r = await pin(root, "plugins/tf");
|
|
62
|
+
expect(r.code).toBe(1);
|
|
63
|
+
expect(r.err).toContain("does not match the integrity its pin states (sha256-AAAA)");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("refuses a path outside the workspace, and a missing argument", async () => {
|
|
67
|
+
const root = workspace();
|
|
68
|
+
expect((await pin(root, "../elsewhere")).code).toBe(1);
|
|
69
|
+
expect((await pin(root, undefined)).code).toBe(1);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant workspace pin <path> [--json]` (#2547, ws-065): print the `integrity`
|
|
3
|
+
* value that pins the plugin at `path`, a directory or file inside the
|
|
4
|
+
* workspace. What the hash covers is in ./pin-integrity.ts. The command reads
|
|
5
|
+
* files only, and says whether a path pin the declaration already has for
|
|
6
|
+
* that path matches.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { relative, resolve, sep } from "node:path";
|
|
10
|
+
import { formatError } from "../cli/format";
|
|
11
|
+
import type { CommandContext } from "../cli/registry";
|
|
12
|
+
import { readDeclaration, WorkspaceReadError } from "./declaration";
|
|
13
|
+
import { integrityOf, PinHashError } from "./pin-integrity";
|
|
14
|
+
import { locateWorkspace } from "./which-chant";
|
|
15
|
+
|
|
16
|
+
const USAGE = "chant workspace pin <path> [--json]";
|
|
17
|
+
|
|
18
|
+
export async function runWorkspacePin(ctx: CommandContext): Promise<number> {
|
|
19
|
+
const { args } = ctx;
|
|
20
|
+
const given = args.extraPositional;
|
|
21
|
+
if (!given) {
|
|
22
|
+
console.error(formatError({ message: "chant workspace pin needs the plugin's path", hint: USAGE }));
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const located = locateWorkspace(process.cwd());
|
|
27
|
+
const declaration = readDeclaration(located.tree, "", { rootChant: true });
|
|
28
|
+
const abs = resolve(process.cwd(), given);
|
|
29
|
+
const rel = relative(located.rootOnDisk, abs).split(sep).join("/");
|
|
30
|
+
if (rel === "" || rel.startsWith("../") || rel === "..") {
|
|
31
|
+
console.error(formatError({ message: `${given} is not inside the workspace`, hint: USAGE }));
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
const { integrity, files } = integrityOf(abs);
|
|
35
|
+
const declared = declaration.pins.find((p) => p.path === rel);
|
|
36
|
+
const state = declared === undefined ? "undeclared" : declared.integrity === null ? "declared-unpinned" : declared.integrity === integrity ? "matches" : "differs";
|
|
37
|
+
if (args.json) {
|
|
38
|
+
console.log(JSON.stringify({ path: rel, integrity, files, pin: state }, null, 2));
|
|
39
|
+
} else {
|
|
40
|
+
console.log(integrity);
|
|
41
|
+
const note = {
|
|
42
|
+
undeclared: `${rel} is not a path pin in ${declaration.file}; add { "path": "${rel}", "integrity": "${integrity}" } to pins`,
|
|
43
|
+
"declared-unpinned": `${rel} is a path pin with no integrity; add "integrity": "${integrity}" to it`,
|
|
44
|
+
matches: `${rel} matches the integrity its pin states`,
|
|
45
|
+
differs: `${rel} does not match the integrity its pin states (${declared?.integrity}); update the pin if the change is intended`,
|
|
46
|
+
}[state];
|
|
47
|
+
console.error(`${files} file${files === 1 ? "" : "s"} hashed. ${note}`);
|
|
48
|
+
}
|
|
49
|
+
return state === "differs" ? 1 : 0;
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (err instanceof WorkspaceReadError || err instanceof PinHashError) {
|
|
52
|
+
console.error(formatError({ message: `${given}: ${err.message}`, hint: USAGE }));
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
6
|
+
import { loadKindRegistry } from "./kinds";
|
|
7
|
+
import { checkPinIntegrity, integrityOf, parseIntegrity, pinnedFiles } from "./pin-integrity";
|
|
8
|
+
|
|
9
|
+
const scratch: string[] = [];
|
|
10
|
+
afterAll(() => {
|
|
11
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
12
|
+
});
|
|
13
|
+
function dir(files: Record<string, string>): string {
|
|
14
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-pin-")));
|
|
15
|
+
scratch.push(root);
|
|
16
|
+
for (const [path, text] of Object.entries(files)) {
|
|
17
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
18
|
+
writeFileSync(join(root, path), text);
|
|
19
|
+
}
|
|
20
|
+
return root;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const tf = { name: "terraform", description: "a Terraform root module", precedence: 400, probe: { anyFile: ["main.tf"] } };
|
|
24
|
+
const plugin = (): Record<string, string> => ({
|
|
25
|
+
"plugins/tf/package.json": JSON.stringify({ name: "tf", exports: { "./workspace-kinds": "./k.json" } }),
|
|
26
|
+
"plugins/tf/k.json": JSON.stringify({ schema: 1, kinds: [tf] }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("integrityOf", () => {
|
|
30
|
+
test("a file is its bytes' SRI digest", () => {
|
|
31
|
+
const root = dir({ "a.txt": "hello\n" });
|
|
32
|
+
const expected = `sha256-${createHash("sha256").update("hello\n").digest("base64")}`;
|
|
33
|
+
expect(integrityOf(join(root, "a.txt"))).toEqual({ integrity: expected, files: 1 });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("a directory is the digest of a manifest of each file's own digest and path, sorted by path", () => {
|
|
37
|
+
const root = dir({ "d/b.txt": "B", "d/a/z.txt": "Z" });
|
|
38
|
+
const h = (s: string) => createHash("sha256").update(s).digest("hex");
|
|
39
|
+
const manifest = `${h("Z")} a/z.txt\n${h("B")} b.txt\n`;
|
|
40
|
+
expect(integrityOf(join(root, "d"))).toEqual({
|
|
41
|
+
integrity: `sha256-${createHash("sha256").update(manifest).digest("base64")}`,
|
|
42
|
+
files: 2,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("it does not depend on the order files were written, and it follows a changed byte, a rename and an added file", () => {
|
|
47
|
+
const base = integrityOf(join(dir({ "d/a": "1", "d/b": "2" }), "d")).integrity;
|
|
48
|
+
expect(integrityOf(join(dir({ "d/b": "2", "d/a": "1" }), "d")).integrity).toBe(base);
|
|
49
|
+
expect(integrityOf(join(dir({ "d/a": "1", "d/b": "3" }), "d")).integrity).not.toBe(base);
|
|
50
|
+
expect(integrityOf(join(dir({ "d/a": "1", "d/c": "2" }), "d")).integrity).not.toBe(base);
|
|
51
|
+
expect(integrityOf(join(dir({ "d/a": "1", "d/b": "2", "d/e": "" }), "d")).integrity).not.toBe(base);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("node_modules and .git are left out, and a symbolic link is refused", () => {
|
|
55
|
+
const root = dir({ "d/a": "1", "d/node_modules/x/index.js": "x", "d/.git/HEAD": "ref" });
|
|
56
|
+
expect(pinnedFiles(join(root, "d"))).toEqual(["a"]);
|
|
57
|
+
symlinkSync("/etc/hosts", join(root, "d", "link"));
|
|
58
|
+
expect(() => integrityOf(join(root, "d"))).toThrow(/link is a symbolic link/);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("sha384 and sha512 are taken with that algorithm", () => {
|
|
62
|
+
const root = dir({ "d/a": "1" });
|
|
63
|
+
expect(integrityOf(join(root, "d"), "sha512").integrity).toMatch(/^sha512-/);
|
|
64
|
+
expect(integrityOf(join(root, "d"), "sha384").integrity).toMatch(/^sha384-/);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("parseIntegrity", () => {
|
|
69
|
+
test("takes the three SRI algorithms and nothing else", () => {
|
|
70
|
+
expect(parseIntegrity("sha256-AAAA")).toEqual({ algorithm: "sha256", digest: "AAAA" });
|
|
71
|
+
expect(parseIntegrity("md5-AAAA")).toBeUndefined();
|
|
72
|
+
expect(parseIntegrity("sha256:AAAA")).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("a path pin's integrity when kinds are loaded (#2547)", () => {
|
|
77
|
+
test("a matching integrity loads the plugin's kinds", () => {
|
|
78
|
+
const root = dir(plugin());
|
|
79
|
+
const { integrity } = integrityOf(join(root, "plugins/tf"));
|
|
80
|
+
const { registry, problems } = loadKindRegistry([{ package: null, version: null, path: "plugins/tf", integrity }], root);
|
|
81
|
+
expect(problems).toEqual([]);
|
|
82
|
+
expect(registry.get("terraform")?.source).toBe("plugins/tf");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("a changed plugin is refused with the expected and actual hash, and none of its kinds load", () => {
|
|
86
|
+
const root = dir(plugin());
|
|
87
|
+
const { integrity } = integrityOf(join(root, "plugins/tf"));
|
|
88
|
+
writeFileSync(join(root, "plugins/tf/k.json"), JSON.stringify({ schema: 1, kinds: [{ ...tf, name: "evil" }] }));
|
|
89
|
+
const { registry, problems } = loadKindRegistry([{ package: null, version: null, path: "plugins/tf", integrity }], root);
|
|
90
|
+
expect(registry.get("evil")).toBeUndefined();
|
|
91
|
+
expect(registry.get("terraform")).toBeUndefined();
|
|
92
|
+
expect(problems).toHaveLength(1);
|
|
93
|
+
expect(problems[0].pin).toBe(0);
|
|
94
|
+
expect(problems[0].message).toContain("plugins/tf: integrity mismatch, so nothing is read from it");
|
|
95
|
+
expect(problems[0].message).toContain(`The pin says ${integrity} and the plugin hashes to sha256-`);
|
|
96
|
+
expect(problems[0].message).toContain("chant workspace pin plugins/tf");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("an added file is a mismatch too", () => {
|
|
100
|
+
const root = dir(plugin());
|
|
101
|
+
const { integrity } = integrityOf(join(root, "plugins/tf"));
|
|
102
|
+
writeFileSync(join(root, "plugins/tf/extra.js"), "x");
|
|
103
|
+
const checked = checkPinIntegrity(join(root, "plugins/tf"), integrity, "plugins/tf");
|
|
104
|
+
expect(checked.ok).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("a pin with no integrity loads as before", () => {
|
|
108
|
+
const root = dir(plugin());
|
|
109
|
+
const { registry, problems } = loadKindRegistry([{ package: null, version: null, path: "plugins/tf", integrity: null }], root);
|
|
110
|
+
expect(problems).toEqual([]);
|
|
111
|
+
expect(registry.get("terraform")).toBeDefined();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("a missing plugin and a malformed integrity are problems, not throws", () => {
|
|
115
|
+
const root = dir(plugin());
|
|
116
|
+
const missing = loadKindRegistry([{ package: null, version: null, path: "plugins/none", integrity: "sha256-AAAA" }], root);
|
|
117
|
+
expect(missing.problems[0].message).toMatch(/can't check its integrity, it does not exist/);
|
|
118
|
+
const bad = checkPinIntegrity(join(root, "plugins/tf"), "nope", "plugins/tf");
|
|
119
|
+
expect(bad).toEqual({ ok: false, message: expect.stringContaining("is not sha256-, sha384- or sha512-") });
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content hashes for plugins a declaration loads by path (#2547, ws-065).
|
|
3
|
+
*
|
|
4
|
+
* A pin of `{ "path": "plugins/kinds", "integrity": "sha256-..." }` says the
|
|
5
|
+
* plugin directory must hash to that value before chant reads anything from
|
|
6
|
+
* it. `chant workspace pin <path>` prints the value to put there.
|
|
7
|
+
*
|
|
8
|
+
* `integrity` is a Subresource Integrity string: `sha256-`, `sha384-` or
|
|
9
|
+
* `sha512-`, then the base64 of the digest. A directory has no single byte
|
|
10
|
+
* stream to hash, so its digest is taken over a manifest that names every
|
|
11
|
+
* file by its path and its own digest, and a file path is hashed directly:
|
|
12
|
+
*
|
|
13
|
+
* - The files are every regular file under the directory, at any depth, except
|
|
14
|
+
* anything inside a `node_modules` or `.git` directory.
|
|
15
|
+
* - Each file's digest is the chosen algorithm over its bytes, as they are on
|
|
16
|
+
* disk: no line-ending change, so a checkout that rewrites line endings
|
|
17
|
+
* hashes differently from one that does not.
|
|
18
|
+
* - The manifest is one line per file, `<hex digest> <path>\n`, in order of
|
|
19
|
+
* the path's UTF-16 code units, with `/` as the separator and the path
|
|
20
|
+
* relative to the directory. The pin is the algorithm over those bytes.
|
|
21
|
+
* - A symbolic link anywhere in the directory is refused: it can point
|
|
22
|
+
* outside the plugin, and what it points at is not what was pinned.
|
|
23
|
+
*
|
|
24
|
+
* The hash covers the working tree. Build output and untracked files inside
|
|
25
|
+
* the directory change it, so pin a directory that holds only committed files.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { createHash } from "node:crypto";
|
|
29
|
+
import { lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
|
|
32
|
+
export type IntegrityAlgorithm = "sha256" | "sha384" | "sha512";
|
|
33
|
+
|
|
34
|
+
const INTEGRITY = /^(sha(?:256|384|512))-([A-Za-z0-9+/]+={0,2})$/;
|
|
35
|
+
|
|
36
|
+
/** The algorithm and base64 digest of an `integrity` value, or undefined when it is not one. */
|
|
37
|
+
export function parseIntegrity(value: string): { algorithm: IntegrityAlgorithm; digest: string } | undefined {
|
|
38
|
+
const m = INTEGRITY.exec(value);
|
|
39
|
+
return m ? { algorithm: m[1] as IntegrityAlgorithm, digest: m[2] } : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Directories a plugin hash never looks inside. */
|
|
43
|
+
const SKIPPED_DIRS = new Set(["node_modules", ".git"]);
|
|
44
|
+
|
|
45
|
+
/** Thrown when a path can't be hashed: it is missing, or holds a symbolic link. */
|
|
46
|
+
export class PinHashError extends Error {}
|
|
47
|
+
|
|
48
|
+
function walk(dir: string, prefix: string, out: string[]): void {
|
|
49
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
50
|
+
const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
51
|
+
if (entry.isSymbolicLink()) throw new PinHashError(`${rel} is a symbolic link, which a pinned plugin may not contain`);
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
if (!SKIPPED_DIRS.has(entry.name)) walk(join(dir, entry.name), rel, out);
|
|
54
|
+
} else if (entry.isFile()) {
|
|
55
|
+
out.push(rel);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The files a directory's hash covers, as paths relative to it, in hash order. */
|
|
61
|
+
export function pinnedFiles(dir: string): string[] {
|
|
62
|
+
const files: string[] = [];
|
|
63
|
+
walk(dir, "", files);
|
|
64
|
+
return files.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The `integrity` value for the plugin at `abs`: a directory by its manifest, a file by its bytes. */
|
|
68
|
+
export function integrityOf(abs: string, algorithm: IntegrityAlgorithm = "sha256"): { integrity: string; files: number } {
|
|
69
|
+
let stat;
|
|
70
|
+
try {
|
|
71
|
+
stat = lstatSync(abs);
|
|
72
|
+
} catch {
|
|
73
|
+
throw new PinHashError("it does not exist");
|
|
74
|
+
}
|
|
75
|
+
if (stat.isSymbolicLink()) throw new PinHashError("it is a symbolic link, which a pinned plugin may not be");
|
|
76
|
+
if (stat.isFile()) {
|
|
77
|
+
return { integrity: `${algorithm}-${createHash(algorithm).update(readFileSync(abs)).digest("base64")}`, files: 1 };
|
|
78
|
+
}
|
|
79
|
+
const files = pinnedFiles(abs);
|
|
80
|
+
const manifest = files.map((f) => `${createHash(algorithm).update(readFileSync(join(abs, ...f.split("/")))).digest("hex")} ${f}\n`).join("");
|
|
81
|
+
return { integrity: `${algorithm}-${createHash(algorithm).update(manifest, "utf8").digest("base64")}`, files: files.length };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type IntegrityCheck = { ok: true } | { ok: false; message: string };
|
|
85
|
+
|
|
86
|
+
/** Compare the plugin at `abs` with the `integrity` a pin states. `label` names the plugin in the message. */
|
|
87
|
+
export function checkPinIntegrity(abs: string, integrity: string, label: string): IntegrityCheck {
|
|
88
|
+
const parsed = parseIntegrity(integrity);
|
|
89
|
+
if (!parsed) return { ok: false, message: `${label}: integrity ${JSON.stringify(integrity)} is not sha256-, sha384- or sha512- and a base64 digest` };
|
|
90
|
+
let actual: string;
|
|
91
|
+
try {
|
|
92
|
+
actual = integrityOf(abs, parsed.algorithm).integrity;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
if (!(err instanceof PinHashError)) throw err;
|
|
95
|
+
return { ok: false, message: `${label}: can't check its integrity, ${err.message}` };
|
|
96
|
+
}
|
|
97
|
+
if (actual === integrity) return { ok: true };
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
message:
|
|
101
|
+
`${label}: integrity mismatch, so nothing is read from it. The pin says ${integrity} and the plugin hashes to ${actual}. ` +
|
|
102
|
+
`If the change is intended, run \`chant workspace pin ${label}\` and put the value it prints in the pin; otherwise restore the plugin`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -473,6 +473,7 @@
|
|
|
473
473
|
"answer-not-candidate",
|
|
474
474
|
"quorum-not-met",
|
|
475
475
|
"answer-in-steward-turn",
|
|
476
|
+
"principal-unidentified",
|
|
476
477
|
"record-not-found",
|
|
477
478
|
"record-closed",
|
|
478
479
|
"record-id-taken",
|
|
@@ -483,6 +484,7 @@
|
|
|
483
484
|
"record-supersedes-conflict",
|
|
484
485
|
"record-remediates-unknown",
|
|
485
486
|
"record-remediates-not-closed",
|
|
487
|
+
"record-seal-mismatch",
|
|
486
488
|
"session-seal-mismatch",
|
|
487
489
|
"session-verdict-unknown-record"
|
|
488
490
|
]
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Principal classes a plugin supplies (#3080, ws-079): the data file, the
|
|
3
|
+
* registry, and how a principal's role grants at base put it in a class.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
9
|
+
import { cleanScratch, scratchDir } from "./__fixtures__/contract-repo";
|
|
10
|
+
import {
|
|
11
|
+
CLASS_NAME,
|
|
12
|
+
classesOf,
|
|
13
|
+
coreClassRegistry,
|
|
14
|
+
loadClassRegistry,
|
|
15
|
+
parseClassData,
|
|
16
|
+
principalClass,
|
|
17
|
+
PRINCIPALS_SCHEMA_ID,
|
|
18
|
+
readPackageClasses,
|
|
19
|
+
treeFiles,
|
|
20
|
+
unknownScopeClasses,
|
|
21
|
+
} from "./principal-classes";
|
|
22
|
+
import { emptyPolicy } from "./trust/policy";
|
|
23
|
+
import type { WorkspaceTree } from "./tree";
|
|
24
|
+
|
|
25
|
+
afterAll(cleanScratch);
|
|
26
|
+
|
|
27
|
+
const data = (classes: unknown[]) => JSON.stringify({ schema: 1, classes });
|
|
28
|
+
const reviewer = { name: "reviewer", description: "people who review decisions", role: "reviewer" };
|
|
29
|
+
const operator = { name: "operator", description: "people who apply infrastructure", role: "ops" };
|
|
30
|
+
|
|
31
|
+
/** A plugin package directory on disk, publishing `classes` at ./workspace-principals. */
|
|
32
|
+
function plugin(root: string, dir: string, classes: unknown[], pkg: Record<string, unknown> = {}): void {
|
|
33
|
+
const abs = join(root, ...dir.split("/"));
|
|
34
|
+
mkdirSync(abs, { recursive: true });
|
|
35
|
+
writeFileSync(join(abs, "package.json"), JSON.stringify({ name: dir.replace(/\//g, "-"), version: "1.0.0", exports: { "./workspace-principals": "./principals.json" }, ...pkg }));
|
|
36
|
+
writeFileSync(join(abs, "principals.json"), data(classes));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("the principals file", () => {
|
|
40
|
+
test("has a versioned schema id and a name grammar shared with writeScope", () => {
|
|
41
|
+
expect(PRINCIPALS_SCHEMA_ID).toBe("https://intentius.io/chant/schemas/workspace/principals/v1/workspace-principals.schema.json");
|
|
42
|
+
expect(CLASS_NAME.test("reviewer")).toBe(true);
|
|
43
|
+
expect(CLASS_NAME.test("x-reviewer")).toBe(false);
|
|
44
|
+
expect(CLASS_NAME.test("Reviewer")).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("a valid file yields its classes in file order, with the pin as source", () => {
|
|
48
|
+
expect(parseClassData(data([reviewer, operator]), "plugins/review")).toEqual({
|
|
49
|
+
classes: [
|
|
50
|
+
{ ...reviewer, source: "plugins/review" },
|
|
51
|
+
{ ...operator, source: "plugins/review" },
|
|
52
|
+
],
|
|
53
|
+
problems: [],
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test.each([
|
|
58
|
+
["not JSON", "{", /not JSON/],
|
|
59
|
+
["a missing role", data([{ name: "reviewer", description: "d" }]), /role/],
|
|
60
|
+
["an unknown field", data([{ ...reviewer, members: ["app"] }]), /additional properties/],
|
|
61
|
+
["an x- name", data([{ ...reviewer, name: "x-reviewer" }]), /pattern/],
|
|
62
|
+
])("refuses %s", (_, text, message) => {
|
|
63
|
+
const read = parseClassData(text, "p");
|
|
64
|
+
expect(read.classes).toEqual([]);
|
|
65
|
+
expect(read.problems.join("\n")).toMatch(message);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("leaves out a core name, a core role, a name twice and a role twice, keeping the rest", () => {
|
|
69
|
+
const read = parseClassData(
|
|
70
|
+
data([
|
|
71
|
+
{ name: "human", description: "d", role: "people" },
|
|
72
|
+
{ name: "bots", description: "d", role: "agent" },
|
|
73
|
+
reviewer,
|
|
74
|
+
{ ...reviewer, role: "other" },
|
|
75
|
+
{ name: "checker", description: "d", role: "reviewer" },
|
|
76
|
+
operator,
|
|
77
|
+
]),
|
|
78
|
+
"p",
|
|
79
|
+
);
|
|
80
|
+
expect(read.classes.map((c) => c.name)).toEqual(["reviewer", "operator"]);
|
|
81
|
+
expect(read.problems).toEqual([
|
|
82
|
+
"p: class human is a core class and can't be supplied by a package",
|
|
83
|
+
"p: class bots names the role agent, which puts a principal in the core agent class",
|
|
84
|
+
"p: class reviewer is listed twice",
|
|
85
|
+
"p: class checker names the role reviewer, which another class in the file names; a role puts a principal in one class",
|
|
86
|
+
]);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("reading a package's classes", () => {
|
|
91
|
+
test("a package exporting no ./workspace-principals supplies none, without a problem", () => {
|
|
92
|
+
const root = scratchDir();
|
|
93
|
+
plugin(root, "kinds-only", [reviewer], { exports: { "./workspace-kinds": "./kinds.json" } });
|
|
94
|
+
expect(loadClassRegistry([{ package: null, version: null, path: "kinds-only" }], root)).toMatchObject({ problems: [] });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("the export must name a JSON file inside the package", () => {
|
|
98
|
+
const files = (pkg: object) => (p: string) => (p === "package.json" ? JSON.stringify(pkg) : undefined);
|
|
99
|
+
expect(readPackageClasses(files({ exports: { "./workspace-principals": "./index.js" } }), "p").problems[0]).toMatch(/must name a .json file/);
|
|
100
|
+
expect(readPackageClasses(files({ exports: { "./workspace-principals": "./../x.json" } }), "p").problems[0]).toMatch(/points outside the package/);
|
|
101
|
+
expect(readPackageClasses(files({ exports: { "./workspace-principals": "./gone.json" } }), "p").problems[0]).toMatch(/does not exist/);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("a path pin is read through a tree when one is given, such as the base revision", () => {
|
|
105
|
+
const files: Record<string, string> = {
|
|
106
|
+
"plugins/review/package.json": JSON.stringify({ exports: { "./workspace-principals": "./principals.json" } }),
|
|
107
|
+
"plugins/review/principals.json": data([reviewer]),
|
|
108
|
+
};
|
|
109
|
+
const tree: WorkspaceTree = {
|
|
110
|
+
label: " at base",
|
|
111
|
+
stat: (p) => (p in files ? "file" : undefined),
|
|
112
|
+
list: () => undefined,
|
|
113
|
+
read: (p) => files[p],
|
|
114
|
+
};
|
|
115
|
+
expect(readPackageClasses(treeFiles(tree, "plugins/review"), "plugins/review").classes.map((c) => c.name)).toEqual(["reviewer"]);
|
|
116
|
+
// The working tree is not consulted: nothing exists on disk at this root.
|
|
117
|
+
const loaded = loadClassRegistry([{ package: null, version: null, path: "plugins/review" }], "/nonexistent", { tree });
|
|
118
|
+
expect(loaded.problems).toEqual([]);
|
|
119
|
+
expect(loaded.registry.names()).toEqual(["human", "agent", "runner", "service", "reviewer"]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("a class name or a role two pins supply is left out, with a problem on the second pin", () => {
|
|
123
|
+
const root = scratchDir();
|
|
124
|
+
plugin(root, "a", [reviewer, operator]);
|
|
125
|
+
plugin(root, "b", [{ ...reviewer, role: "approver" }]);
|
|
126
|
+
plugin(root, "c", [{ name: "applier", description: "d", role: "ops" }]);
|
|
127
|
+
const pins = ["a", "b", "c"].map((path) => ({ package: null, version: null, path }));
|
|
128
|
+
const { registry, problems } = loadClassRegistry(pins, root);
|
|
129
|
+
expect(registry.names()).toEqual(["human", "agent", "runner", "service"]);
|
|
130
|
+
expect(problems).toEqual([
|
|
131
|
+
{ pin: 1, message: "class reviewer is supplied by both a and b; a class name has one source" },
|
|
132
|
+
{ pin: 2, message: "class applier (c) names the role ops, which class operator (a) names; a role puts a principal in one class" },
|
|
133
|
+
]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("a package pin that is not installed is a problem, and a pin naming neither is skipped", () => {
|
|
137
|
+
const root = scratchDir();
|
|
138
|
+
const { problems } = loadClassRegistry(
|
|
139
|
+
[
|
|
140
|
+
{ package: "absent", version: "1.0.0", path: null },
|
|
141
|
+
{ package: null, version: null, path: null },
|
|
142
|
+
],
|
|
143
|
+
root,
|
|
144
|
+
);
|
|
145
|
+
expect(problems).toEqual([{ pin: 0, message: "pinned package absent is not installed; install it to read the kinds it supplies" }]);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("a principal's classes", () => {
|
|
150
|
+
const root = scratchDir();
|
|
151
|
+
plugin(root, "review", [reviewer, operator]);
|
|
152
|
+
const { registry } = loadClassRegistry([{ package: null, version: null, path: "review" }], root);
|
|
153
|
+
const policy = { ...emptyPolicy("x"), roles: { reviewer: ["Rev@example.com", "ci@example.com"], ops: ["rev@example.com"], runner: ["ci@example.com"] } };
|
|
154
|
+
|
|
155
|
+
test("come from role grants at base, core first, then the domain classes in order", () => {
|
|
156
|
+
expect(classesOf(registry, policy, "rev@example.com")).toEqual(["reviewer", "operator"]);
|
|
157
|
+
expect(classesOf(registry, policy, "ci@example.com")).toEqual(["runner", "reviewer"]);
|
|
158
|
+
expect(classesOf(registry, policy, "lex00")).toEqual([]);
|
|
159
|
+
expect(classesOf(registry, policy, null)).toEqual([]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("write scope judges a principal by the first, and human is the rest", () => {
|
|
163
|
+
expect(principalClass(policy, "rev@example.com", registry)).toBe("reviewer");
|
|
164
|
+
expect(principalClass(policy, "ci@example.com", registry)).toBe("runner");
|
|
165
|
+
expect(principalClass(policy, "lex00", registry)).toBe("human");
|
|
166
|
+
// Without the plugin's classes, a domain principal is a human (the behaviour before #3080).
|
|
167
|
+
expect(principalClass(policy, "rev@example.com")).toBe("human");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("a writeScope key no class has is unknown", () => {
|
|
171
|
+
expect(unknownScopeClasses({ human: {}, reviewer: {}, auditor: {} }, registry)).toEqual(["auditor"]);
|
|
172
|
+
expect(unknownScopeClasses({ reviewer: {} }, coreClassRegistry())).toEqual(["reviewer"]);
|
|
173
|
+
expect(unknownScopeClasses(null, registry)).toEqual([]);
|
|
174
|
+
});
|
|
175
|
+
});
|