@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
package/src/workspace/work.ts
CHANGED
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
* - `implements`: each decision it names, with that decision's state;
|
|
18
18
|
* - `acceptance`, for a kind with acceptance criteria (#2772): how many of the
|
|
19
19
|
* record's criteria passing evidence of the expected verification meets;
|
|
20
|
+
* - `contract`, for a kind with a contract link (#3147): the contract record
|
|
21
|
+
* the item builds, with its state, read from the contract kind at the same
|
|
22
|
+
* revision;
|
|
20
23
|
*
|
|
21
24
|
* and each decision `implementedBy`, the work records naming it. The
|
|
22
25
|
* warnings are closed codes. `work-done-gap-open` is not raised here: it
|
|
@@ -48,6 +51,12 @@ export const WORK_WARNING_CODES = [
|
|
|
48
51
|
"work-acceptance-unmet",
|
|
49
52
|
/** A passing `manual` verdict on a criterion names the record's implementer, so it does not count: a manual verdict comes from someone else (#2772). */
|
|
50
53
|
"work-acceptance-self-verified",
|
|
54
|
+
/** The record names a contract no record of the kind's contract kind has (#3147). */
|
|
55
|
+
"work-contract-unknown",
|
|
56
|
+
/** The record names a contract whose state is not approved, such as a draft (#3147). */
|
|
57
|
+
"work-contract-undecided",
|
|
58
|
+
/** The record names a builder tier its kind's `work.tier.tiers` does not list (#3147). */
|
|
59
|
+
"work-tier-unknown",
|
|
51
60
|
] as const satisfies readonly ReasonCode[];
|
|
52
61
|
export type WorkWarningCode = (typeof WORK_WARNING_CODES)[number];
|
|
53
62
|
|
|
@@ -58,6 +67,49 @@ export interface WorkLink {
|
|
|
58
67
|
state: string | null;
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
/**
|
|
71
|
+
* One decision-point answer about a work item (#3147), as `records --json`
|
|
72
|
+
* joins it from the answer kind the work kind names: the answer records whose
|
|
73
|
+
* `constrains` names the item's id. The item never copies them.
|
|
74
|
+
*/
|
|
75
|
+
export interface WorkAnswer {
|
|
76
|
+
/** The answer record's id, such as understand-0123456789ab. */
|
|
77
|
+
id: string;
|
|
78
|
+
/** The decision point it answers. */
|
|
79
|
+
point: string | null;
|
|
80
|
+
/** escalated, proposed or answered. */
|
|
81
|
+
state: string | null;
|
|
82
|
+
/** The answer, or null while there is none. */
|
|
83
|
+
answer: string | boolean | null;
|
|
84
|
+
/** The people who answered or confirmed it, empty when a table or model did. */
|
|
85
|
+
answeredBy: string[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The answers about each work item: every answer record whose `constrains`
|
|
90
|
+
* names the item's id, in path order, by item id.
|
|
91
|
+
*/
|
|
92
|
+
export function workAnswers(answerRecords: readonly RecordEntry[], ids: Iterable<string>): Map<string, WorkAnswer[]> {
|
|
93
|
+
const out = new Map<string, WorkAnswer[]>();
|
|
94
|
+
for (const id of ids) out.set(id, []);
|
|
95
|
+
for (const a of answerRecords) {
|
|
96
|
+
if (a.id === null || a.data === null) continue;
|
|
97
|
+
for (const target of idList(a.data, "constrains")) {
|
|
98
|
+
const list = out.get(target);
|
|
99
|
+
if (!list || list.some((x) => x.id === a.id)) continue;
|
|
100
|
+
const answer = a.data.answer;
|
|
101
|
+
list.push({
|
|
102
|
+
id: a.id,
|
|
103
|
+
point: typeof a.data.point === "string" ? a.data.point : null,
|
|
104
|
+
state: a.state,
|
|
105
|
+
answer: typeof answer === "string" || typeof answer === "boolean" ? answer : null,
|
|
106
|
+
answeredBy: idList(a.data, "answered_by"),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
61
113
|
/** A decision as a work read lists it: its state and the work records implementing it. */
|
|
62
114
|
export interface DecisionWork {
|
|
63
115
|
id: string;
|
|
@@ -174,6 +226,16 @@ export async function applyWork(loaded: LoadedRecordKind, entries: RecordEntry[]
|
|
|
174
226
|
for (const e of entries) if (e.id !== null && !byId.has(e.id)) byId.set(e.id, e);
|
|
175
227
|
const closed = new Set(kind.closedStates);
|
|
176
228
|
|
|
229
|
+
// The contract kind a work item names its contract in (#3147), read at the same revision.
|
|
230
|
+
let contracts: { kind: LoadedRecordKind; byId: Map<string, RecordEntry> } | undefined;
|
|
231
|
+
if (work.contract) {
|
|
232
|
+
const contractKind = await loadRecordKind(work.contract.kind, dirname(loaded.file));
|
|
233
|
+
const contractRead = await readRecords(contractKind, { root: options.root, source: options.source });
|
|
234
|
+
const contractById = new Map<string, RecordEntry>();
|
|
235
|
+
for (const c of contractRead.records) if (c.id !== null && !contractById.has(c.id)) contractById.set(c.id, c);
|
|
236
|
+
contracts = { kind: contractKind, byId: contractById };
|
|
237
|
+
}
|
|
238
|
+
|
|
177
239
|
// Records in a needs cycle: those that reach themselves.
|
|
178
240
|
const inCycle = new Set<string>();
|
|
179
241
|
for (const start of byId.keys()) {
|
|
@@ -242,6 +304,26 @@ export async function applyWork(loaded: LoadedRecordKind, entries: RecordEntry[]
|
|
|
242
304
|
});
|
|
243
305
|
}
|
|
244
306
|
}
|
|
307
|
+
if (contracts && work.contract) {
|
|
308
|
+
const named = e.data[work.contract.field];
|
|
309
|
+
if (typeof named === "string" && named !== "") {
|
|
310
|
+
const c = contracts.byId.get(named);
|
|
311
|
+
e.contract = { id: named, state: c?.state ?? null };
|
|
312
|
+
if (!c) {
|
|
313
|
+
e.warnings.push({ code: "work-contract-unknown", message: `names the contract ${named}, which no record in ${work.contract.kind} has` });
|
|
314
|
+
} else if (!isDecided(contracts.kind.kind, c.state)) {
|
|
315
|
+
e.warnings.push({ code: "work-contract-undecided", message: `names the contract ${named}, which is ${c.state ?? "in no state"}: the work may build a contract nobody approved` });
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
e.contract = null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (work.tier) {
|
|
322
|
+
const tier = e.data[work.tier.field];
|
|
323
|
+
if (typeof tier === "string" && !work.tier.tiers.includes(tier)) {
|
|
324
|
+
e.warnings.push({ code: "work-tier-unknown", message: `names the builder tier ${tier}, which the kind's tiers (${work.tier.tiers.join(", ")}) do not list` });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
245
327
|
if (e.state !== null && closed.has(e.state) && typeof e.data[work.closedOn] !== "string") {
|
|
246
328
|
e.warnings.push({ code: "work-closed-without-date", message: `${e.id ?? e.path} is ${e.state} and has no ${work.closedOn}` });
|
|
247
329
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://intentius.io/chant/schemas/workspace/principals/v1/workspace-principals.schema.json",
|
|
4
|
+
"title": "workspace-principals.json",
|
|
5
|
+
"description": "Principal classes a plugin supplies (#2524 D5, #3080, ws-079). A package publishes this file as data at its ./workspace-principals subpath, beside ./workspace-kinds. chant reads it with the file system and never imports the package. A class is a name for the principals holding one role in the trust policy at base; the declaration's writeScope may then give it an entry.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["schema", "classes"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "An editor hint. chant ignores it."
|
|
12
|
+
},
|
|
13
|
+
"$comment": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "A note for people. chant ignores it."
|
|
16
|
+
},
|
|
17
|
+
"schema": {
|
|
18
|
+
"const": 1,
|
|
19
|
+
"description": "The principal classes format version."
|
|
20
|
+
},
|
|
21
|
+
"classes": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"minItems": 1,
|
|
24
|
+
"items": {
|
|
25
|
+
"$ref": "#/$defs/class"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"patternProperties": {
|
|
30
|
+
"^x-": true
|
|
31
|
+
},
|
|
32
|
+
"additionalProperties": false,
|
|
33
|
+
"$defs": {
|
|
34
|
+
"class": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"required": ["name", "description", "role"],
|
|
37
|
+
"properties": {
|
|
38
|
+
"name": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"pattern": "^(?!x-)[a-z][a-z0-9-]{0,39}$",
|
|
41
|
+
"description": "The class's name, as a key of the declaration's writeScope names it. human, agent, runner and service are the core classes and can't be supplied."
|
|
42
|
+
},
|
|
43
|
+
"description": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"minLength": 1,
|
|
46
|
+
"description": "One line saying who is in the class, for listings and error messages."
|
|
47
|
+
},
|
|
48
|
+
"role": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
51
|
+
"description": "The role whose grant in the trust policy at base (.chant/trust.json roles) puts a principal in this class. The agent, runner and service roles belong to the core classes and can't be named here. One role puts a principal in one class."
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"patternProperties": {
|
|
55
|
+
"^x-": true
|
|
56
|
+
},
|
|
57
|
+
"additionalProperties": false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write scope per member and record kind, and agent sessions bound to one
|
|
3
|
+
* member (#2548, ws-067), on a workspace built in a throwaway git repository.
|
|
4
|
+
*
|
|
5
|
+
* main holds a declaration with two members, app and design, the workspace's
|
|
6
|
+
* own decision kind and a decision kind design declares, a trust policy that
|
|
7
|
+
* grants ci@example.com the runner role, and:
|
|
8
|
+
*
|
|
9
|
+
* - writeScope.agent: decisions may be proposed (new) and reviewed, not amended;
|
|
10
|
+
* - writeScope.runner: app's files only, and decision reviews only;
|
|
11
|
+
* - agents app-agent (app, listing bot@example.com) and design-agent (design).
|
|
12
|
+
*
|
|
13
|
+
* The record writes, the MCP tools, `check --changes` and `workspace agent`
|
|
14
|
+
* all read the scope from main, so a working-tree edit can't widen it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
20
|
+
import { cleanScratch, contract, git, REPO, repo, writeFiles } from "./__fixtures__/contract-repo";
|
|
21
|
+
import { agentSession } from "./agent-cli";
|
|
22
|
+
import agentSchema from "./agent.schema.json";
|
|
23
|
+
import { checkChanges, type ChangesDocument } from "./changes";
|
|
24
|
+
import { runDeclarationChecks } from "./checks";
|
|
25
|
+
import changesSchema from "./changes.schema.json";
|
|
26
|
+
import { formatChanges } from "./changes-cli";
|
|
27
|
+
import { parseDeclaration, WorkspaceReadError } from "./declaration";
|
|
28
|
+
import { parseFrontMatter } from "./records";
|
|
29
|
+
import { amendRecord, newRecord, renderRecord, reviewRecord } from "./records-write";
|
|
30
|
+
import { emptyPolicy } from "./trust/policy";
|
|
31
|
+
import { judgePath, judgeRecord, principalClass, resolveWriter, WriteScopeError } from "./write-scope";
|
|
32
|
+
import { createWorkspaceTools } from "../cli/mcp/workspace-tools";
|
|
33
|
+
|
|
34
|
+
const DECISIONS = join(REPO, "docs", "design", "decisions");
|
|
35
|
+
const KIND = "decisions/decision.kind.mjs";
|
|
36
|
+
const DESIGN_KIND = "design/decisions/decision.kind.mjs";
|
|
37
|
+
|
|
38
|
+
type Data = Record<string, unknown>;
|
|
39
|
+
|
|
40
|
+
const SAMPLE = (() => {
|
|
41
|
+
const fm = parseFrontMatter(readFileSync(join(DECISIONS, "ws-003-seal-scope.md"), "utf-8"));
|
|
42
|
+
if (!fm.ok) throw new Error(fm.message);
|
|
43
|
+
return fm.value;
|
|
44
|
+
})();
|
|
45
|
+
|
|
46
|
+
const decided = (over: Data): Data => ({ ...structuredClone(SAMPLE), ...over });
|
|
47
|
+
const proposal = (over: Data): Data => decided({ state: "proposed", choice: null, decided_by: null, decided_on: null, ...over });
|
|
48
|
+
const record = (d: Data) => renderRecord(d, `\n# ${String(d.title)}\n`);
|
|
49
|
+
|
|
50
|
+
const SCOPE = {
|
|
51
|
+
agent: { records: { decision: ["new", "review"] } },
|
|
52
|
+
runner: { members: ["app"], records: { decision: ["review"] } },
|
|
53
|
+
};
|
|
54
|
+
const AGENTS = [
|
|
55
|
+
{ name: "app-agent", member: "app", principals: ["bot@example.com"] },
|
|
56
|
+
{ name: "design-agent", member: "design" },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
function declaration(scope: unknown = SCOPE, agents: unknown = AGENTS): string {
|
|
60
|
+
return JSON.stringify(
|
|
61
|
+
{
|
|
62
|
+
name: "studio",
|
|
63
|
+
schema: 1,
|
|
64
|
+
members: [
|
|
65
|
+
{ name: "app", dir: "app", kind: "other", because: "a plain Node server" },
|
|
66
|
+
{ name: "design", dir: "design", kind: "other", because: "screens", records: [{ kind: "decisions/decision.kind.mjs", name: "screen" }] },
|
|
67
|
+
],
|
|
68
|
+
records: [{ kind: KIND }],
|
|
69
|
+
writeScope: scope,
|
|
70
|
+
agents,
|
|
71
|
+
},
|
|
72
|
+
null,
|
|
73
|
+
2,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let root: string;
|
|
78
|
+
const sha: Record<string, string> = {};
|
|
79
|
+
|
|
80
|
+
function commit(message: string, email?: string): string {
|
|
81
|
+
git(root, "add", "-A");
|
|
82
|
+
git(root, ...(email ? ["-c", `user.email=${email}`] : []), "commit", "-q", "-m", message);
|
|
83
|
+
return git(root, "rev-parse", "HEAD");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
beforeAll(() => {
|
|
87
|
+
const kind = readFileSync(join(DECISIONS, "decision.kind.mjs"), "utf-8");
|
|
88
|
+
const schema = readFileSync(join(DECISIONS, "decision.schema.json"), "utf-8");
|
|
89
|
+
root = repo({
|
|
90
|
+
"chant.workspace.json": declaration(),
|
|
91
|
+
".chant/trust.json": JSON.stringify({ schema: 1, roles: { runner: ["ci@example.com"] } }),
|
|
92
|
+
[KIND]: kind,
|
|
93
|
+
"decisions/decision.schema.json": schema,
|
|
94
|
+
"decisions/ws-001-one.md": record(decided({ id: "ws-001", title: "One" })),
|
|
95
|
+
[DESIGN_KIND]: kind,
|
|
96
|
+
"design/decisions/decision.schema.json": schema,
|
|
97
|
+
"design/decisions/ws-001-screen.md": record(decided({ id: "ws-001", title: "Screen" })),
|
|
98
|
+
"app/server.mjs": "export const port = 8080;\n",
|
|
99
|
+
"design/spec.md": "# Spec\n",
|
|
100
|
+
});
|
|
101
|
+
sha.base = commit("the workspace");
|
|
102
|
+
git(root, "branch", "-M", "main");
|
|
103
|
+
});
|
|
104
|
+
afterAll(cleanScratch);
|
|
105
|
+
|
|
106
|
+
const code = (doc: object) => ("error" in doc ? (doc as { error: { code: string } }).error.code : null);
|
|
107
|
+
const newDecision = (title: string) => JSON.stringify(proposal({ id: undefined, title }));
|
|
108
|
+
|
|
109
|
+
describe("the declaration's writeScope and agents (#2548)", () => {
|
|
110
|
+
test("are read with their defaults", () => {
|
|
111
|
+
const d = parseDeclaration(declaration(), "chant.workspace.json");
|
|
112
|
+
expect(d.writeScope).toEqual({
|
|
113
|
+
agent: { members: null, records: { decision: ["new", "review"] }, protected: [], pointer: "/writeScope/agent" },
|
|
114
|
+
runner: { members: ["app"], records: { decision: ["review"] }, protected: [], pointer: "/writeScope/runner" },
|
|
115
|
+
});
|
|
116
|
+
expect(d.agents.map((a) => [a.name, a.member, a.principals])).toEqual([
|
|
117
|
+
["app-agent", "app", ["bot@example.com"]],
|
|
118
|
+
["design-agent", "design", []],
|
|
119
|
+
]);
|
|
120
|
+
const none = parseDeclaration(JSON.stringify({ name: "w", schema: 1, members: [] }), "chant.workspace.json");
|
|
121
|
+
expect([none.writeScope, none.agents]).toEqual([null, []]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test.each([
|
|
125
|
+
["an agent bound to no declared member", declaration(SCOPE, [{ name: "a", member: "nowhere" }]), /not a declared member/],
|
|
126
|
+
["a scope naming no declared member", declaration({ human: { members: ["nowhere"] } }), /does not declare/],
|
|
127
|
+
["a principal two sessions list", declaration(SCOPE, [{ name: "a", member: "app", principals: ["p"] }, { name: "b", member: "design", principals: ["p"] }]), /already listed by agent a/],
|
|
128
|
+
["two sessions with one name", declaration(SCOPE, [{ name: "a", member: "app" }, { name: "a", member: "design" }]), /already used/],
|
|
129
|
+
["a members list on the agent class", declaration({ agent: { members: ["app"] } }), /members/],
|
|
130
|
+
["an unknown verb", declaration({ agent: { records: { decision: ["delete"] } } }), /must be equal to one of the allowed values|delete/],
|
|
131
|
+
])("refuse %s", (_, text, message) => {
|
|
132
|
+
let err: unknown;
|
|
133
|
+
try {
|
|
134
|
+
parseDeclaration(text, "chant.workspace.json");
|
|
135
|
+
} catch (e) {
|
|
136
|
+
err = e;
|
|
137
|
+
}
|
|
138
|
+
expect(err).toBeInstanceOf(WorkspaceReadError);
|
|
139
|
+
expect((err as WorkspaceReadError).code).toBe("declaration-invalid");
|
|
140
|
+
expect((err as Error).message).toMatch(message);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("judging a write", () => {
|
|
145
|
+
const decl = parseDeclaration(declaration(), "chant.workspace.json");
|
|
146
|
+
const policy = { ...emptyPolicy("x"), roles: { runner: ["CI@example.com"], agent: ["model@example.com"] } };
|
|
147
|
+
const ws = { name: "decision", declaredName: null, member: null, declared: true };
|
|
148
|
+
const screen = { name: "decision", declaredName: "screen", member: "design", declared: true };
|
|
149
|
+
|
|
150
|
+
test("a principal's class comes from its role grants at base, and human is the rest", () => {
|
|
151
|
+
expect(principalClass(policy, "ci@example.com")).toBe("runner");
|
|
152
|
+
expect(principalClass(policy, "model@example.com")).toBe("agent");
|
|
153
|
+
expect(principalClass(policy, "lex00")).toBe("human");
|
|
154
|
+
expect(principalClass(policy, null)).toBe("human");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("a session is named, or found by a principal it lists; an unknown one is refused", () => {
|
|
158
|
+
expect(resolveWriter(decl, policy, { agent: "app-agent" }).agent?.member).toBe("app");
|
|
159
|
+
expect(resolveWriter(decl, policy, { principal: "Bot@example.com" })).toMatchObject({ class: "agent", agent: { name: "app-agent" } });
|
|
160
|
+
expect(resolveWriter(decl, policy, { principal: "lex00" })).toMatchObject({ class: "human", agent: null });
|
|
161
|
+
expect(() => resolveWriter(decl, policy, { agent: "ghost" })).toThrow(WriteScopeError);
|
|
162
|
+
expect(() => resolveWriter(null, policy, { agent: "ghost" })).toThrow(/no workspace declaration/);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("an agent writes its own member, the workspace's kinds as its records rule allows, and nothing of another member", () => {
|
|
166
|
+
const app = resolveWriter(decl, policy, { agent: "app-agent" });
|
|
167
|
+
expect(judgePath(decl, app, "app/server.mjs")).toEqual({ ok: true });
|
|
168
|
+
expect(judgePath(decl, app, "design/spec.md")).toMatchObject({ ok: false, code: "write-scope-member" });
|
|
169
|
+
expect(judgePath(decl, app, "chant.workspace.json")).toMatchObject({ ok: false, code: "write-scope-member", message: expect.stringContaining("in no member") });
|
|
170
|
+
expect(judgeRecord(decl, app, ws, "new")).toEqual({ ok: true });
|
|
171
|
+
expect(judgeRecord(decl, app, ws, "review")).toEqual({ ok: true });
|
|
172
|
+
expect(judgeRecord(decl, app, ws, "amend")).toMatchObject({ ok: false, code: "write-scope-kind", message: expect.stringContaining("may new, review decision records, and not amend") });
|
|
173
|
+
expect(judgeRecord(decl, app, ws, "delete")).toMatchObject({ ok: false, code: "write-scope-kind" });
|
|
174
|
+
expect(judgeRecord(decl, app, screen, "new")).toMatchObject({ ok: false, code: "write-scope-member" });
|
|
175
|
+
const design = resolveWriter(decl, policy, { agent: "design-agent" });
|
|
176
|
+
expect(judgeRecord(decl, design, screen, "new")).toEqual({ ok: true });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("an agent by role with no session reaches only the workspace's own kinds", () => {
|
|
180
|
+
const model = resolveWriter(decl, policy, { principal: "model@example.com" });
|
|
181
|
+
expect(model).toMatchObject({ class: "agent", agent: null });
|
|
182
|
+
expect(judgePath(decl, model, "app/server.mjs")).toMatchObject({ ok: false, message: expect.stringContaining("which no agent session lists") });
|
|
183
|
+
expect(judgeRecord(decl, model, ws, "new")).toEqual({ ok: true });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("a class with an entry is held to its members and kinds; a class with none is not restricted", () => {
|
|
187
|
+
const ci = resolveWriter(decl, policy, { principal: "ci@example.com" });
|
|
188
|
+
expect(judgePath(decl, ci, "app/server.mjs")).toEqual({ ok: true });
|
|
189
|
+
expect(judgePath(decl, ci, "design/spec.md")).toMatchObject({ ok: false, code: "write-scope-member" });
|
|
190
|
+
expect(judgeRecord(decl, ci, ws, "review")).toEqual({ ok: true });
|
|
191
|
+
expect(judgeRecord(decl, ci, ws, "new")).toMatchObject({ ok: false, code: "write-scope-kind", message: expect.stringContaining("may review decision records, and not new") });
|
|
192
|
+
expect(judgeRecord(decl, ci, screen, "review")).toMatchObject({ ok: false, code: "write-scope-member" });
|
|
193
|
+
const human = resolveWriter(decl, policy, { principal: "lex00" });
|
|
194
|
+
expect(judgePath(decl, human, "design/spec.md")).toEqual({ ok: true });
|
|
195
|
+
expect(judgeRecord(decl, human, screen, "delete")).toEqual({ ok: true });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("the declared name of a kind is a key for its records rule too", () => {
|
|
199
|
+
const d = parseDeclaration(declaration({ agent: { records: { screen: ["new"] } } }), "chant.workspace.json");
|
|
200
|
+
const design = resolveWriter(d, policy, { agent: "design-agent" });
|
|
201
|
+
expect(judgeRecord(d, design, screen, "new")).toEqual({ ok: true });
|
|
202
|
+
expect(judgeRecord(d, design, ws, "new")).toMatchObject({ ok: false, code: "write-scope-kind", message: expect.stringContaining("does not list the kind") });
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("the record writes refuse a write outside the writer's scope (CLI functions and MCP tools)", () => {
|
|
207
|
+
test("an agent session proposes and reviews a decision, and may not amend one", async () => {
|
|
208
|
+
const created = await newRecord({ kind: KIND, fields: newDecision("Logs"), cwd: root, agent: "app-agent", dryRun: true });
|
|
209
|
+
expect(code(created)).toBeNull();
|
|
210
|
+
const reviewed = await reviewRecord({ kind: KIND, id: "ws-001", verdict: "agree", by: "app-agent", cwd: root, agent: "app-agent", dryRun: true });
|
|
211
|
+
expect(code(reviewed)).toBeNull();
|
|
212
|
+
const amended = await amendRecord({ kind: KIND, id: "ws-001", fields: JSON.stringify({ state: "ratified" }), cwd: root, agent: "app-agent", dryRun: true });
|
|
213
|
+
expect(amended).toMatchObject({ error: { code: "write-scope-kind", message: expect.stringContaining("agent session app-agent, bound to member app,") } });
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("an agent session may not write another member's records", async () => {
|
|
217
|
+
expect(code(await newRecord({ kind: DESIGN_KIND, fields: newDecision("Screen two"), cwd: root, agent: "app-agent", dryRun: true }))).toBe("write-scope-member");
|
|
218
|
+
expect(code(await newRecord({ kind: DESIGN_KIND, fields: newDecision("Screen two"), cwd: root, agent: "design-agent", dryRun: true }))).toBeNull();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("an unknown session is refused; a write naming no session is a human's, and a listed principal is its session's", async () => {
|
|
222
|
+
expect(code(await newRecord({ kind: KIND, fields: newDecision("Logs"), cwd: root, agent: "ghost", dryRun: true }))).toBe("agent-unknown");
|
|
223
|
+
expect(code(await amendRecord({ kind: KIND, id: "ws-001", fields: JSON.stringify({ title: "One, retitled" }), cwd: root, dryRun: true }))).toBe("amend-supersede-instead");
|
|
224
|
+
expect(code(await amendRecord({ kind: KIND, id: "ws-001", fields: JSON.stringify({ state: "ratified" }), by: "bot@example.com", cwd: root, dryRun: true }))).toBe("write-scope-kind");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("a runner, by its role at base, reviews and does not propose", async () => {
|
|
228
|
+
expect(code(await reviewRecord({ kind: KIND, id: "ws-001", verdict: "agree", by: "ci@example.com", cwd: root, dryRun: true }))).toBeNull();
|
|
229
|
+
expect(code(await newRecord({ kind: KIND, fields: newDecision("Logs"), by: "ci@example.com", cwd: root, dryRun: true }))).toBe("write-scope-kind");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("the scope is read at base: widening it in the working tree changes nothing until it is merged", async () => {
|
|
233
|
+
writeFiles(root, { "chant.workspace.json": declaration({ agent: { records: { decision: ["new", "review", "amend"] } } }) });
|
|
234
|
+
try {
|
|
235
|
+
expect(code(await amendRecord({ kind: KIND, id: "ws-001", fields: JSON.stringify({ state: "ratified" }), cwd: root, agent: "app-agent", dryRun: true }))).toBe("write-scope-kind");
|
|
236
|
+
} finally {
|
|
237
|
+
writeFiles(root, { "chant.workspace.json": declaration() });
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("the MCP record tools write in the server's session, with the CLI's codes", async () => {
|
|
242
|
+
const tools = createWorkspaceTools({ cwd: root, agent: "app-agent", chantCommand: ["false"] });
|
|
243
|
+
const tool = (name: string) => tools.find((t) => t.definition.name === name)!.handler;
|
|
244
|
+
const amended = (await tool("records-amend")({ id: "ws-001", kind: KIND, fields: { state: "ratified" }, dryRun: true })) as object;
|
|
245
|
+
expect(code(amended)).toBe("write-scope-kind");
|
|
246
|
+
const other = (await tool("records-new")({ kind: DESIGN_KIND, record: proposal({ id: undefined, title: "Screen two", state: undefined }), dryRun: true })) as object;
|
|
247
|
+
expect(code(other)).toBe("write-scope-member");
|
|
248
|
+
const own = (await tool("records-new")({ kind: KIND, record: proposal({ id: undefined, title: "Logs", state: undefined }), dryRun: true })) as object;
|
|
249
|
+
expect(code(own)).toBeNull();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe("check --changes fails a commit outside its writer's scope (#2548)", () => {
|
|
254
|
+
const changes = contract(changesSchema);
|
|
255
|
+
let doc: Exclude<ChangesDocument, { error: unknown }>;
|
|
256
|
+
let failed: boolean;
|
|
257
|
+
|
|
258
|
+
beforeAll(async () => {
|
|
259
|
+
git(root, "checkout", "-q", "-b", "work");
|
|
260
|
+
const trailer = (name: string) => `\n\nChant-Agent: ${name}`;
|
|
261
|
+
writeFiles(root, { "app/server.mjs": "export const port = 9090;\n" });
|
|
262
|
+
sha.inMember = commit(`app: a port${trailer("app-agent")}`);
|
|
263
|
+
writeFiles(root, { "design/spec.md": "# Spec, by the app agent\n" });
|
|
264
|
+
sha.otherMember = commit(`design: an edit${trailer("app-agent")}`);
|
|
265
|
+
writeFiles(root, { "decisions/ws-002-logs.md": record(proposal({ id: "ws-002", title: "Logs" })) });
|
|
266
|
+
sha.proposed = commit(`a proposal${trailer("app-agent")}`);
|
|
267
|
+
writeFiles(root, { "decisions/ws-001-one.md": record(decided({ id: "ws-001", title: "One", reviews: [{ reviewer: "app-agent", verdict: "agree", on: "2026-09-30" }] })) });
|
|
268
|
+
sha.reviewed = commit(`a review${trailer("app-agent")}`);
|
|
269
|
+
writeFiles(root, { "decisions/ws-001-one.md": record(decided({ id: "ws-001", title: "One, decided again", reviews: [{ reviewer: "app-agent", verdict: "agree", on: "2026-09-30" }] })) });
|
|
270
|
+
sha.amended = commit(`an amendment${trailer("app-agent")}`);
|
|
271
|
+
writeFiles(root, { "chant.workspace.json": declaration({ agent: { records: { decision: ["new", "review", "amend"] } } }) });
|
|
272
|
+
sha.widened = commit(`widen my scope${trailer("app-agent")}`);
|
|
273
|
+
writeFiles(root, { "app/server.mjs": "export const port = 9191;\n" });
|
|
274
|
+
sha.ghost = commit(`a ghost${trailer("ghost")}`);
|
|
275
|
+
writeFiles(root, { "design/spec.md": "# Spec, by CI\n" });
|
|
276
|
+
sha.runner = commit("ci: an edit", "ci@example.com");
|
|
277
|
+
writeFiles(root, { "design/spec.md": "# Spec, by a person\n", "chant.workspace.json": declaration() });
|
|
278
|
+
sha.human = commit("a person's edit");
|
|
279
|
+
({ doc, failed } = (await checkChanges({ cwd: root, range: "main..work" })) as { doc: Exclude<ChangesDocument, { error: unknown }>; failed: boolean });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("the document validates and the check fails", () => {
|
|
283
|
+
changes.expectValid(doc);
|
|
284
|
+
expect(failed).toBe(true);
|
|
285
|
+
expect(doc.ok).toBe(false);
|
|
286
|
+
expect(doc.scope).toMatchObject({ base: sha.base, restricted: ["agent", "runner"], agents: ["app-agent", "design-agent"] });
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("each commit is judged as its session, its role, or a person", () => {
|
|
290
|
+
const by = Object.fromEntries(doc.scope!.commits.map((c) => [c.commit, [c.class, c.agent, c.principal, c.attested]]));
|
|
291
|
+
expect(by[sha.inMember]).toEqual(["agent", "app-agent", "t@example.com", false]);
|
|
292
|
+
expect(by[sha.ghost]).toEqual(["agent", "ghost", "t@example.com", false]);
|
|
293
|
+
expect(by[sha.runner]).toEqual(["runner", null, "ci@example.com", false]);
|
|
294
|
+
expect(by[sha.human]).toEqual(["human", null, "t@example.com", false]);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("a finding for each write outside scope, and none for one inside it", () => {
|
|
298
|
+
const found = doc.scope!.findings.map((f) => [Object.entries(sha).find(([, s]) => s === f.commit)![0], f.code, f.path, f.verb]);
|
|
299
|
+
expect(found).toEqual([
|
|
300
|
+
["otherMember", "write-scope-member", "design/spec.md", null],
|
|
301
|
+
["amended", "write-scope-kind", "decisions/ws-001-one.md", "amend"],
|
|
302
|
+
["widened", "write-scope-member", "chant.workspace.json", null],
|
|
303
|
+
["ghost", "agent-unknown", null, null],
|
|
304
|
+
["runner", "write-scope-member", "design/spec.md", null],
|
|
305
|
+
]);
|
|
306
|
+
expect(formatChanges(doc)).toContain(`error write-scope-kind: ${sha.amended.slice(0, 8)} changes decisions/ws-001-one.md: agent session app-agent`);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("an empty range judges no commit", async () => {
|
|
310
|
+
const { doc: before } = await checkChanges({ cwd: root, range: `${sha.base}~0..${sha.base}` });
|
|
311
|
+
expect("error" in before ? before : before.scope).toMatchObject({ base: sha.base, commits: [], findings: [] });
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("chant workspace agent: what a session reloads from (#2548)", () => {
|
|
316
|
+
const agent = contract(agentSchema);
|
|
317
|
+
|
|
318
|
+
test("the session, its member, its scope and the spec, read from the repository", async () => {
|
|
319
|
+
const doc = await agentSession({ cwd: root, name: "design-agent" });
|
|
320
|
+
agent.expectValid(doc);
|
|
321
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
322
|
+
expect(doc.workspace).toEqual({ name: "studio", root: ".", scopeFrom: "base" });
|
|
323
|
+
expect(doc.agent).toEqual({ name: "design-agent", member: { name: "design", dir: "design", kind: "other" }, principals: [] });
|
|
324
|
+
expect(doc.scope).toEqual({
|
|
325
|
+
members: ["design"],
|
|
326
|
+
records: [
|
|
327
|
+
{ path: KIND, kind: "decision", name: null, member: null, verbs: ["new", "review"] },
|
|
328
|
+
{ path: DESIGN_KIND, kind: "decision", name: "screen", member: "design", verbs: ["new", "review"] },
|
|
329
|
+
],
|
|
330
|
+
protected: [],
|
|
331
|
+
});
|
|
332
|
+
expect(doc.spec.kinds.map((k) => k.path)).toEqual([KIND, DESIGN_KIND]);
|
|
333
|
+
expect(doc.spec.records.map((r) => `${r.kind}/${r.id}`)).toContain("decision/ws-001");
|
|
334
|
+
expect(doc.reload).toEqual(["chant workspace agent design-agent --json", "chant workspace records --current --json"]);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("an unknown session is an error document", async () => {
|
|
338
|
+
const doc = await agentSession({ cwd: root, name: "ghost" });
|
|
339
|
+
agent.expectValid(doc);
|
|
340
|
+
expect(code(doc)).toBe("agent-unknown");
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe("principal classes a plugin defines (#3080)", () => {
|
|
345
|
+
const PLUGIN = "plugins/review";
|
|
346
|
+
const PRINCIPALS = JSON.stringify({ schema: 1, classes: [{ name: "reviewer", description: "people who review decisions", role: "reviewer" }] });
|
|
347
|
+
const REVIEWER_SCOPE = { reviewer: { members: ["design"], records: { decision: ["review"] } } };
|
|
348
|
+
let dom: string;
|
|
349
|
+
const at: Record<string, string> = {};
|
|
350
|
+
|
|
351
|
+
function domainDeclaration(scope: unknown): string {
|
|
352
|
+
return JSON.stringify({ ...JSON.parse(declaration(scope, [])), pins: [{ path: PLUGIN }] }, null, 2);
|
|
353
|
+
}
|
|
354
|
+
function commitIn(message: string, email?: string): string {
|
|
355
|
+
git(dom, "add", "-A");
|
|
356
|
+
git(dom, ...(email ? ["-c", `user.email=${email}`] : []), "commit", "-q", "-m", message);
|
|
357
|
+
return git(dom, "rev-parse", "HEAD");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
beforeAll(() => {
|
|
361
|
+
const kind = readFileSync(join(DECISIONS, "decision.kind.mjs"), "utf-8");
|
|
362
|
+
const schema = readFileSync(join(DECISIONS, "decision.schema.json"), "utf-8");
|
|
363
|
+
dom = repo({
|
|
364
|
+
"chant.workspace.json": domainDeclaration(REVIEWER_SCOPE),
|
|
365
|
+
".chant/trust.json": JSON.stringify({ schema: 1, roles: { reviewer: ["rev@example.com"] } }),
|
|
366
|
+
[`${PLUGIN}/package.json`]: JSON.stringify({ name: "review-classes", version: "1.0.0", exports: { "./workspace-principals": "./workspace-principals.json" } }),
|
|
367
|
+
[`${PLUGIN}/workspace-principals.json`]: PRINCIPALS,
|
|
368
|
+
[KIND]: kind,
|
|
369
|
+
"decisions/decision.schema.json": schema,
|
|
370
|
+
"decisions/ws-001-one.md": record(decided({ id: "ws-001", title: "One" })),
|
|
371
|
+
[DESIGN_KIND]: kind,
|
|
372
|
+
"design/decisions/decision.schema.json": schema,
|
|
373
|
+
"design/decisions/ws-001-screen.md": record(decided({ id: "ws-001", title: "Screen" })),
|
|
374
|
+
"app/server.mjs": "export const port = 8080;\n",
|
|
375
|
+
"design/spec.md": "# Spec\n",
|
|
376
|
+
});
|
|
377
|
+
at.base = commitIn("the workspace");
|
|
378
|
+
git(dom, "branch", "-M", "main");
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("writeScope takes a domain class's name as a key, in file order", () => {
|
|
382
|
+
const d = parseDeclaration(domainDeclaration({ reviewer: { records: { decision: ["review"] } }, human: {} }), "chant.workspace.json");
|
|
383
|
+
expect(Object.keys(d.writeScope!)).toEqual(["reviewer", "human"]);
|
|
384
|
+
expect(() => parseDeclaration(domainDeclaration({ "x-reviewer": {} }), "chant.workspace.json")).toThrow(WorkspaceReadError);
|
|
385
|
+
expect(() => parseDeclaration(domainDeclaration({ Reviewer: {} }), "chant.workspace.json")).toThrow(WorkspaceReadError);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("a principal holding the class's role at base is held to its entry; anyone else is a human", async () => {
|
|
389
|
+
expect(code(await reviewRecord({ kind: KIND, id: "ws-001", verdict: "agree", by: "rev@example.com", cwd: dom, dryRun: true }))).toBeNull();
|
|
390
|
+
const proposed = await newRecord({ kind: KIND, fields: newDecision("Logs"), by: "rev@example.com", cwd: dom, dryRun: true });
|
|
391
|
+
expect(proposed).toMatchObject({ error: { code: "write-scope-kind", message: expect.stringContaining("rev@example.com (reviewer) may review decision records, and not new") } });
|
|
392
|
+
expect(code(await newRecord({ kind: KIND, fields: newDecision("Logs"), by: "lex00", cwd: dom, dryRun: true }))).toBeNull();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("a path-pinned plugin's classes are read at base: remapping the class in the working tree changes nothing", async () => {
|
|
396
|
+
writeFiles(dom, { [`${PLUGIN}/workspace-principals.json`]: JSON.stringify({ schema: 1, classes: [{ name: "reviewer", description: "d", role: "nobody" }] }) });
|
|
397
|
+
try {
|
|
398
|
+
expect(code(await newRecord({ kind: KIND, fields: newDecision("Logs"), by: "rev@example.com", cwd: dom, dryRun: true }))).toBe("write-scope-kind");
|
|
399
|
+
} finally {
|
|
400
|
+
writeFiles(dom, { [`${PLUGIN}/workspace-principals.json`]: PRINCIPALS });
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("check --changes judges a commit by its author's domain class", async () => {
|
|
405
|
+
git(dom, "checkout", "-q", "-b", "work");
|
|
406
|
+
writeFiles(dom, { "app/server.mjs": "export const port = 9090;\n" });
|
|
407
|
+
at.outside = commitIn("app: a port", "rev@example.com");
|
|
408
|
+
writeFiles(dom, { "design/spec.md": "# Spec, reviewed\n" });
|
|
409
|
+
at.inside = commitIn("design: a note", "rev@example.com");
|
|
410
|
+
const { doc } = (await checkChanges({ cwd: dom, range: "main..work" })) as { doc: Exclude<ChangesDocument, { error: unknown }> };
|
|
411
|
+
contract(changesSchema).expectValid(doc);
|
|
412
|
+
expect(doc.scope).toMatchObject({ restricted: ["reviewer"] });
|
|
413
|
+
expect(doc.scope!.commits.map((c) => [c.commit, c.class])).toEqual([
|
|
414
|
+
[at.outside, "reviewer"],
|
|
415
|
+
[at.inside, "reviewer"],
|
|
416
|
+
]);
|
|
417
|
+
expect(doc.scope!.findings.map((f) => [f.commit, f.code, f.path, f.class])).toEqual([[at.outside, "write-scope-member", "app/server.mjs", "reviewer"]]);
|
|
418
|
+
git(dom, "checkout", "-q", "main");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe("a class no pinned package supplies fails closed", () => {
|
|
422
|
+
beforeAll(() => {
|
|
423
|
+
writeFiles(dom, { "chant.workspace.json": domainDeclaration({ ...REVIEWER_SCOPE, auditor: { members: ["design"] } }) });
|
|
424
|
+
at.unknown = commitIn("an auditor class no plugin supplies");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("a writer judged human is refused; one in a known class is judged by it", async () => {
|
|
428
|
+
const refused = await newRecord({ kind: KIND, fields: newDecision("Logs"), by: "lex00", cwd: dom, dryRun: true });
|
|
429
|
+
expect(refused).toMatchObject({ error: { code: "write-scope-class-unknown", message: expect.stringContaining("writeScope.auditor") } });
|
|
430
|
+
expect(code(await newRecord({ kind: KIND, fields: newDecision("Logs"), cwd: dom, dryRun: true }))).toBe("write-scope-class-unknown");
|
|
431
|
+
expect(code(await reviewRecord({ kind: KIND, id: "ws-001", verdict: "agree", by: "rev@example.com", cwd: dom, dryRun: true }))).toBeNull();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("check --changes reports the human's commit as a whole", async () => {
|
|
435
|
+
git(dom, "checkout", "-q", "-b", "audit");
|
|
436
|
+
writeFiles(dom, { "design/spec.md": "# Spec, by a person\n" });
|
|
437
|
+
const human = commitIn("a person's edit");
|
|
438
|
+
const { doc } = (await checkChanges({ cwd: dom, range: "main..audit" })) as { doc: Exclude<ChangesDocument, { error: unknown }> };
|
|
439
|
+
contract(changesSchema).expectValid(doc);
|
|
440
|
+
expect(doc.scope).toMatchObject({ restricted: ["reviewer", "auditor"] });
|
|
441
|
+
expect(doc.scope!.findings.map((f) => [f.id, f.path, f.class])).toEqual([[`finding:write-scope-class-unknown:${human}`, null, "human"]]);
|
|
442
|
+
git(dom, "checkout", "-q", "main");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("workspace check reports the unknown class with WSP003", async () => {
|
|
446
|
+
const report = await runDeclarationChecks(dom, undefined, { gather: false });
|
|
447
|
+
const found = report.diagnostics.filter((d) => d.ruleId === "WSP003");
|
|
448
|
+
expect(found).toHaveLength(1);
|
|
449
|
+
expect(found[0].message).toMatch(/writeScope names the principal class auditor, which no core class or pinned package supplies/);
|
|
450
|
+
expect(found[0].message).toMatch(/known classes: human, agent, runner, service, reviewer/);
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test("workspace check reports an unreadable principals file with WSP002", async () => {
|
|
455
|
+
const root = repo({
|
|
456
|
+
"chant.workspace.json": domainDeclaration(REVIEWER_SCOPE),
|
|
457
|
+
[`${PLUGIN}/package.json`]: JSON.stringify({ exports: { "./workspace-principals": "./workspace-principals.json" } }),
|
|
458
|
+
[`${PLUGIN}/workspace-principals.json`]: JSON.stringify({ schema: 1, classes: [{ name: "human", description: "d", role: "people" }] }),
|
|
459
|
+
"app/server.mjs": "",
|
|
460
|
+
"design/spec.md": "",
|
|
461
|
+
});
|
|
462
|
+
const report = await runDeclarationChecks(root, undefined, { gather: false });
|
|
463
|
+
expect(report.diagnostics.filter((d) => d.ruleId === "WSP002").map((d) => d.message)).toEqual([`${PLUGIN}: class human is a core class and can't be supplied by a package`]);
|
|
464
|
+
expect(report.diagnostics.filter((d) => d.ruleId === "WSP003").map((d) => d.message)).toEqual([expect.stringContaining("principal class reviewer")]);
|
|
465
|
+
});
|
|
466
|
+
});
|