@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
|
@@ -165,6 +165,11 @@
|
|
|
165
165
|
"source-harvest-not-proposed",
|
|
166
166
|
"record-state-not-initial",
|
|
167
167
|
"ratify-quorum-not-met",
|
|
168
|
+
"write-scope-member",
|
|
169
|
+
"write-scope-kind",
|
|
170
|
+
"write-scope-class-unknown",
|
|
171
|
+
"agent-unknown",
|
|
172
|
+
"principal-unidentified",
|
|
168
173
|
"record-unparseable",
|
|
169
174
|
"record-schema-invalid",
|
|
170
175
|
"record-id-duplicate",
|
|
@@ -172,6 +177,7 @@
|
|
|
172
177
|
"record-supersedes-conflict",
|
|
173
178
|
"record-remediates-unknown",
|
|
174
179
|
"record-remediates-not-closed",
|
|
180
|
+
"record-seal-mismatch",
|
|
175
181
|
"session-seal-mismatch",
|
|
176
182
|
"session-verdict-unknown-record"
|
|
177
183
|
]
|
|
@@ -259,6 +259,11 @@
|
|
|
259
259
|
"review-sign-failed",
|
|
260
260
|
"session-unknown",
|
|
261
261
|
"session-not-open",
|
|
262
|
+
"write-scope-member",
|
|
263
|
+
"write-scope-kind",
|
|
264
|
+
"write-scope-class-unknown",
|
|
265
|
+
"agent-unknown",
|
|
266
|
+
"principal-unidentified",
|
|
262
267
|
"record-unparseable",
|
|
263
268
|
"record-schema-invalid",
|
|
264
269
|
"record-id-duplicate",
|
|
@@ -266,6 +271,7 @@
|
|
|
266
271
|
"record-supersedes-conflict",
|
|
267
272
|
"record-remediates-unknown",
|
|
268
273
|
"record-remediates-not-closed",
|
|
274
|
+
"record-seal-mismatch",
|
|
269
275
|
"session-seal-mismatch",
|
|
270
276
|
"session-verdict-unknown-record"
|
|
271
277
|
]
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* decision reviews that cite it.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
9
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
+
import yaml from "js-yaml";
|
|
11
12
|
import { afterAll, describe, expect, test } from "vitest";
|
|
12
13
|
import { cleanScratch, commitAll, contract, REPO } from "./__fixtures__/contract-repo";
|
|
13
14
|
import { reviewed, sessionText, SESSIONS_KIND as SESSIONS, sessionsRepo as fixture } from "./__fixtures__/sessions";
|
|
@@ -34,14 +35,30 @@ describe("the session kind (#2673)", () => {
|
|
|
34
35
|
expect(doc.records[0].citedBy).toEqual([]);
|
|
35
36
|
});
|
|
36
37
|
|
|
37
|
-
test("the seal is the
|
|
38
|
+
test("the seal is the whole-file JCS rule the docs give (#2546)", () => {
|
|
38
39
|
const file = join(REPO, "reference-workspace", "design", "sessions", "S-0001-first-walk-of-the-reference-decisions.md");
|
|
39
|
-
const awk = execFileSync("sh", ["-c", `awk 'NR==1&&/^---$/{f=1;print;next} f&&/^---$/{f=0} f&&/^closed_digest:/{next} {print}' "$1" | shasum -a 256`, "sh", file], { encoding: "utf-8" });
|
|
40
40
|
const text = readFileSync(file, "utf-8");
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// The rule worked by hand: the front matter as JSON without closed_digest, the text below it, keys sorted, no whitespace.
|
|
42
|
+
const [, front, body] = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(text)!;
|
|
43
|
+
const { closed_digest: _seal, ...core } = yaml.load(front, { schema: yaml.JSON_SCHEMA }) as Record<string, unknown>;
|
|
44
|
+
const sorted = (v: unknown): unknown =>
|
|
45
|
+
v === null || typeof v !== "object" ? v : Array.isArray(v) ? v.map(sorted) : Object.fromEntries(Object.keys(v).sort().map((k) => [k, sorted((v as Record<string, unknown>)[k])]));
|
|
46
|
+
const byHand = `sha256:${createHash("sha256").update(JSON.stringify(sorted({ body, core })), "utf8").digest("hex")}`;
|
|
47
|
+
expect(sessionSeal(text, "closed_digest")).toBe(byHand);
|
|
48
|
+
expect(text).toContain(`closed_digest: "${byHand}"`);
|
|
43
49
|
// CRLF line endings hash as LF.
|
|
44
|
-
expect(sessionSeal(text.replace(/\n/g, "\r\n"), "closed_digest")).toBe(
|
|
50
|
+
expect(sessionSeal(text.replace(/\n/g, "\r\n"), "closed_digest")).toBe(byHand);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("a session sealed by the text rule before #2546 reads as session-seal-mismatch, naming the old rule", async () => {
|
|
54
|
+
const root = fixture();
|
|
55
|
+
const file = join(root, "design", "sessions", "S-0001-first-walk-of-the-reference-decisions.md");
|
|
56
|
+
const text = readFileSync(file, "utf-8");
|
|
57
|
+
const old = createHash("sha256").update(text.replace(/^closed_digest: .*\n/m, ""), "utf8").digest("hex");
|
|
58
|
+
writeFileSync(file, text.replace(/^closed_digest: .*$/m, `closed_digest: "${old}"`));
|
|
59
|
+
const s1 = (await read(root)).records[0];
|
|
60
|
+
expect(s1.reasons.map((r) => r.code)).toContain("session-seal-mismatch");
|
|
61
|
+
expect(s1.reasons.find((r) => r.code === "session-seal-mismatch")!.message).toMatch(/the text rule chant used before #2546/);
|
|
45
62
|
});
|
|
46
63
|
|
|
47
64
|
test("a closed session edited after it closed is session-seal-mismatch", async () => {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uncommitted records in the read contract (#3160): each record of a
|
|
3
|
+
* working-tree read says whether HEAD holds it as it is, the document names
|
|
4
|
+
* the branch, head and base, and `--uncommitted` lists only what HEAD
|
|
5
|
+
* doesn't hold. The documents validate against records.schema.json.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
9
|
+
import { cpSync, mkdtempSync, readFileSync, realpathSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import Ajv2020 from "ajv/dist/2020";
|
|
13
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
14
|
+
import { CONFORMANCE_FIXTURE_DIR, UNCOMMITTED_DECISION } from "./conformance";
|
|
15
|
+
import { queryRecords, type RecordsDocument } from "./records-cli";
|
|
16
|
+
import schema from "./records.schema.json";
|
|
17
|
+
|
|
18
|
+
const validate = new Ajv2020({ strict: true, allErrors: true }).compile(schema);
|
|
19
|
+
const KIND = "decisions/decision.kind.mjs";
|
|
20
|
+
|
|
21
|
+
type Result = Extract<RecordsDocument, { records: unknown }>;
|
|
22
|
+
|
|
23
|
+
function result(doc: RecordsDocument): Result {
|
|
24
|
+
expect(validate(doc), JSON.stringify(validate.errors, null, 2)).toBe(true);
|
|
25
|
+
if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
|
|
26
|
+
return doc;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const scratch: string[] = [];
|
|
30
|
+
afterAll(() => {
|
|
31
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const GIT_ENV = { ...process.env, GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_NOSYSTEM: "1" };
|
|
35
|
+
function git(cwd: string, ...args: string[]): string {
|
|
36
|
+
return execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@localhost", "-c", "commit.gpgsign=false", ...args], { cwd, env: GIT_ENV, encoding: "utf-8" }).trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const DECIDED = readFileSync(join(CONFORMANCE_FIXTURE_DIR, "decisions", "fix-001-how-the-app-is-deployed.md"), "utf-8");
|
|
40
|
+
/** A decided record with id `fix-<n>`. */
|
|
41
|
+
const decided = (n: string) => DECIDED.replace('id: "fix-001"', `id: "fix-${n}"`);
|
|
42
|
+
/** A proposed record with id `fix-<n>`. */
|
|
43
|
+
const proposed = (n: string) => UNCOMMITTED_DECISION.text.replace('id: "fix-002"', `id: "fix-${n}"`);
|
|
44
|
+
const file = (n: string) => `decisions/fix-${n}-a-record.md`;
|
|
45
|
+
|
|
46
|
+
/** A directory with the decision kind and its schema, and no records. */
|
|
47
|
+
function kindDir(): string {
|
|
48
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-records-uncommitted-")));
|
|
49
|
+
scratch.push(root);
|
|
50
|
+
cpSync(join(CONFORMANCE_FIXTURE_DIR, "decisions"), join(root, "decisions"), { recursive: true, filter: (src) => !src.endsWith(".md") });
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A repository whose main holds fix-001, fix-003 and fix-005, on a work branch one commit past main. */
|
|
55
|
+
function repository(): { root: string; main: string } {
|
|
56
|
+
const root = kindDir();
|
|
57
|
+
for (const n of ["001", "003", "005"]) writeFileSync(join(root, file(n)), decided(n));
|
|
58
|
+
git(root, "init", "--quiet", "--initial-branch=main");
|
|
59
|
+
git(root, "add", "-A");
|
|
60
|
+
git(root, "commit", "--quiet", "-m", "records");
|
|
61
|
+
const main = git(root, "rev-parse", "HEAD");
|
|
62
|
+
git(root, "checkout", "--quiet", "-b", "chant/work/w-1");
|
|
63
|
+
writeFileSync(join(root, "notes.txt"), "work\n");
|
|
64
|
+
git(root, "add", "notes.txt");
|
|
65
|
+
git(root, "commit", "--quiet", "-m", "work");
|
|
66
|
+
return { root, main };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe("records in the working tree (#3160)", () => {
|
|
70
|
+
test("each record is committed, modified or new against HEAD, and the checkout names its branch, head, base and deleted records", async () => {
|
|
71
|
+
const { root, main } = repository();
|
|
72
|
+
writeFileSync(join(root, file("001")), `${decided("001")}\nA line kept in the working tree.\n`);
|
|
73
|
+
writeFileSync(join(root, file("002")), proposed("002"));
|
|
74
|
+
writeFileSync(join(root, file("004")), proposed("004"));
|
|
75
|
+
git(root, "add", file("004"));
|
|
76
|
+
unlinkSync(join(root, file("003")));
|
|
77
|
+
const head = git(root, "rev-parse", "HEAD");
|
|
78
|
+
|
|
79
|
+
const doc = result(await queryRecords({ kind: KIND, cwd: root }));
|
|
80
|
+
expect(doc.uncommitted).toBe(false);
|
|
81
|
+
expect(doc.records.map((r) => [r.id, r.worktree])).toEqual([
|
|
82
|
+
["fix-001", "modified"],
|
|
83
|
+
["fix-002", "new"],
|
|
84
|
+
["fix-004", "new"],
|
|
85
|
+
["fix-005", "committed"],
|
|
86
|
+
]);
|
|
87
|
+
expect(doc.checkout).toEqual({ branch: "chant/work/w-1", head, base: main, baseFrom: "main", deleted: [file("003")] });
|
|
88
|
+
|
|
89
|
+
const only = result(await queryRecords({ kind: KIND, cwd: root, uncommitted: true }));
|
|
90
|
+
expect(only.uncommitted).toBe(true);
|
|
91
|
+
expect(only.records.map((r) => [r.id, r.worktree])).toEqual([
|
|
92
|
+
["fix-001", "modified"],
|
|
93
|
+
["fix-002", "new"],
|
|
94
|
+
["fix-004", "new"],
|
|
95
|
+
]);
|
|
96
|
+
expect(only.checkout).toEqual(doc.checkout);
|
|
97
|
+
expect(only.summary).toEqual({ total: 3, valid: 3, invalid: 0, superseded: 0 });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("a record changed back to what HEAD holds, or touched without a change, is committed, and the read leaves the index alone", async () => {
|
|
101
|
+
const { root } = repository();
|
|
102
|
+
const path = join(root, file("001"));
|
|
103
|
+
writeFileSync(path, decided("001"));
|
|
104
|
+
const index = join(root, ".git", "index");
|
|
105
|
+
const before = { bytes: readFileSync(index), mtime: statSync(index).mtimeMs };
|
|
106
|
+
const doc = result(await queryRecords({ kind: KIND, cwd: root, uncommitted: true }));
|
|
107
|
+
expect(doc.records).toEqual([]);
|
|
108
|
+
expect(doc.checkout?.deleted).toEqual([]);
|
|
109
|
+
expect(readFileSync(index).equals(before.bytes)).toBe(true);
|
|
110
|
+
expect(statSync(index).mtimeMs).toBe(before.mtime);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("--base names the target the base is forked from", async () => {
|
|
114
|
+
const { root } = repository();
|
|
115
|
+
const head = git(root, "rev-parse", "HEAD");
|
|
116
|
+
const doc = result(await queryRecords({ kind: KIND, cwd: root, base: "HEAD" }));
|
|
117
|
+
expect(doc.checkout).toMatchObject({ head, base: head, baseFrom: "flag" });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("a detached HEAD has no branch, and a repository with no commits has no head and only new records", async () => {
|
|
121
|
+
const { root } = repository();
|
|
122
|
+
git(root, "checkout", "--quiet", "--detach");
|
|
123
|
+
expect(result(await queryRecords({ kind: KIND, cwd: root })).checkout?.branch).toBeNull();
|
|
124
|
+
|
|
125
|
+
const empty = kindDir();
|
|
126
|
+
writeFileSync(join(empty, file("001")), decided("001"));
|
|
127
|
+
git(empty, "init", "--quiet", "--initial-branch=main");
|
|
128
|
+
const doc = result(await queryRecords({ kind: KIND, cwd: empty, uncommitted: true }));
|
|
129
|
+
expect(doc.checkout).toEqual({ branch: "main", head: null, base: null, baseFrom: null, deleted: [] });
|
|
130
|
+
expect(doc.records.map((r) => r.worktree)).toEqual(["new"]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("a read at a revision has no checkout and no worktree", async () => {
|
|
134
|
+
const { root } = repository();
|
|
135
|
+
writeFileSync(join(root, file("002")), proposed("002"));
|
|
136
|
+
const doc = result(await queryRecords({ kind: KIND, cwd: root, at: "HEAD" }));
|
|
137
|
+
expect(doc.checkout).toBeUndefined();
|
|
138
|
+
expect(doc.records.every((r) => r.worktree === undefined)).toBe(true);
|
|
139
|
+
await expect(queryRecords({ kind: KIND, cwd: root, at: "HEAD", uncommitted: true })).rejects.toThrow(/takes no --at/);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("outside git a read has no checkout, and --uncommitted fails with not-a-git-repository", async () => {
|
|
143
|
+
const root = kindDir();
|
|
144
|
+
writeFileSync(join(root, file("001")), decided("001"));
|
|
145
|
+
const doc = result(await queryRecords({ kind: KIND, cwd: root }));
|
|
146
|
+
expect(doc.checkout).toBeUndefined();
|
|
147
|
+
expect(doc.records[0].worktree).toBeUndefined();
|
|
148
|
+
const failed = await queryRecords({ kind: KIND, cwd: root, uncommitted: true });
|
|
149
|
+
expect(validate(failed)).toBe(true);
|
|
150
|
+
expect(failed).toMatchObject({ error: { code: "not-a-git-repository" } });
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("the schema holds an --uncommitted document to its promise", () => {
|
|
154
|
+
const head = "a".repeat(40);
|
|
155
|
+
const doc = {
|
|
156
|
+
$schema: schema.$id,
|
|
157
|
+
contract: 1,
|
|
158
|
+
kind: { name: "decision", schema: "urn:x", file: KIND },
|
|
159
|
+
at: null,
|
|
160
|
+
current: false,
|
|
161
|
+
uncommitted: true,
|
|
162
|
+
checkout: { branch: "main", head, base: head, baseFrom: "main", deleted: [] },
|
|
163
|
+
records: [{ id: "fix-001", path: file("001"), state: "decided", valid: true, reasons: [], supersededBy: null, remediatedBy: [], data: {}, worktree: "new" }],
|
|
164
|
+
summary: { total: 1, valid: 1, invalid: 0, superseded: 0 },
|
|
165
|
+
};
|
|
166
|
+
expect(validate(doc)).toBe(true);
|
|
167
|
+
expect(validate({ ...doc, records: [{ ...doc.records[0], worktree: "committed" }] })).toBe(false);
|
|
168
|
+
const { checkout: _checkout, ...noCheckout } = doc;
|
|
169
|
+
expect(validate(noCheckout)).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -10,7 +10,7 @@ import { cpSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync
|
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
13
|
-
import { parseFrontMatter, recordTextDigest as textDigest } from "./records";
|
|
13
|
+
import { parseFrontMatter, recordSeal, recordTextDigest as textDigest } from "./records";
|
|
14
14
|
import { queryRecords } from "./records-cli";
|
|
15
15
|
import { allocateId, amendRecord, newRecord, renderRecord, reviewRecord, toYaml } from "./records-write";
|
|
16
16
|
|
|
@@ -21,7 +21,7 @@ const KIND = "decisions/decision.kind.mjs";
|
|
|
21
21
|
type Data = Record<string, unknown>;
|
|
22
22
|
|
|
23
23
|
/** The digest of a decision: the kind names a ratified state, so the state leaves the digest with the reviews and seal (#2873). */
|
|
24
|
-
const recordTextDigest = (text: string) => textDigest(text, ["reviews", "seal", "state"]);
|
|
24
|
+
const recordTextDigest = (text: string) => textDigest(text, ["reviews", "seal", "state", "closed_digest"]);
|
|
25
25
|
|
|
26
26
|
const SAMPLE = (() => {
|
|
27
27
|
const fm = parseFrontMatter(readFileSync(join(DECISIONS, "ws-003-seal-scope.md"), "utf-8"));
|
|
@@ -287,7 +287,7 @@ describe("records amend", () => {
|
|
|
287
287
|
const evidence = [{ title: "A spec", url: "https://example.com/spec" }];
|
|
288
288
|
expect(await amendRecord({ kind: KIND, id: "ws-001", fields: fields({ evidence }), cwd: dir })).toMatchObject({ changed: ["evidence"] });
|
|
289
289
|
for (const by of ["alice", "bob"]) await reviewRecord({ kind: KIND, id: "ws-001", verdict: "agree", by, cwd: dir });
|
|
290
|
-
expect(await amendRecord({ kind: KIND, id: "ws-001", fields: fields({ state: "ratified" }), cwd: dir })).toMatchObject({ changed: ["state"] });
|
|
290
|
+
expect(await amendRecord({ kind: KIND, id: "ws-001", fields: fields({ state: "ratified" }), cwd: dir })).toMatchObject({ changed: ["state", "closed_digest"] });
|
|
291
291
|
put("ws-006-six.md", decision({ id: "ws-006", title: "Six" }));
|
|
292
292
|
expect(code(await amendRecord({ kind: KIND, id: "ws-006", fields: fields({ state: "withdrawn" }), cwd: dir }))).toBe("amend-supersede-instead");
|
|
293
293
|
});
|
|
@@ -353,12 +353,43 @@ describe("ratifying against the quorum (#2873)", () => {
|
|
|
353
353
|
await agree("alice");
|
|
354
354
|
await agree("bob");
|
|
355
355
|
const judged = recordTextDigest(text("ws-001-one.md"));
|
|
356
|
-
expect(await ratify()).toMatchObject({ changed: ["state"] });
|
|
356
|
+
expect(await ratify()).toMatchObject({ changed: ["state", "closed_digest"] });
|
|
357
357
|
expect(data("ws-001-one.md").state).toBe("ratified");
|
|
358
358
|
expect(recordTextDigest(text("ws-001-one.md"))).toBe(judged);
|
|
359
359
|
expect(await quorumOf("ws-001")).toMatchObject({ need: 2, agreed: 2, met: true, notCounted: [] });
|
|
360
360
|
});
|
|
361
361
|
|
|
362
|
+
test("ratifying writes the whole-file seal, which covers the reviews and the state the digest leaves out (#2546)", async () => {
|
|
363
|
+
await agree("alice");
|
|
364
|
+
await agree("bob");
|
|
365
|
+
const judged = textDigest(text("ws-001-one.md"), ["reviews", "seal", "state", "closed_digest"]);
|
|
366
|
+
await ratify();
|
|
367
|
+
const sealed = text("ws-001-one.md");
|
|
368
|
+
expect(data("ws-001-one.md").closed_digest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
369
|
+
expect(data("ws-001-one.md").closed_digest).toBe(recordSeal(sealed, "closed_digest"));
|
|
370
|
+
// The closed_digest line goes last in the front matter, and the digest leaves it out.
|
|
371
|
+
expect(sealed).toMatch(/\nclosed_digest: "sha256:[0-9a-f]{64}"\n---\n/);
|
|
372
|
+
expect(textDigest(sealed, ["reviews", "seal", "state", "closed_digest"])).toBe(judged);
|
|
373
|
+
const read = async () => (await queryRecords({ kind: KIND, cwd: dir })) as { records: { id: string; valid: boolean; reasons: { code: string; message: string }[] }[] };
|
|
374
|
+
expect((await read()).records.find((r) => r.id === "ws-001")).toMatchObject({ valid: true, reasons: [] });
|
|
375
|
+
// A verdict, a state or a byte of the body edited by hand after the close breaks the seal.
|
|
376
|
+
for (const edit of [
|
|
377
|
+
(t: string) => t.replace('verdict: "agree"', 'verdict: "dissent"\n note: "no"'),
|
|
378
|
+
(t: string) => t.replace('state: "ratified"', 'state: "superseded"'),
|
|
379
|
+
(t: string) => `${t}\nAn afterthought.\n`,
|
|
380
|
+
]) {
|
|
381
|
+
writeFileSync(join(dir, "decisions", "ws-001-one.md"), edit(sealed));
|
|
382
|
+
const r = (await read()).records.find((x) => x.id === "ws-001")!;
|
|
383
|
+
expect(r.reasons.map((x) => x.code)).toContain("record-seal-mismatch");
|
|
384
|
+
}
|
|
385
|
+
// CRLF line endings, or a value quoted another way, leave the seal as it was.
|
|
386
|
+
writeFileSync(join(dir, "decisions", "ws-001-one.md"), sealed.replace('area: "D4"', "area: 'D4'").replace(/\n/g, "\r\n"));
|
|
387
|
+
expect((await read()).records.find((r) => r.id === "ws-001")).toMatchObject({ valid: true });
|
|
388
|
+
// And a closed record is never written again, so the seal can't be set by hand either.
|
|
389
|
+
writeFileSync(join(dir, "decisions", "ws-001-one.md"), sealed);
|
|
390
|
+
expect(code(await amendRecord({ kind: KIND, id: "ws-002", fields: fields({ closed_digest: `sha256:${"0".repeat(64)}` }), cwd: dir }))).toBe("write-input-invalid");
|
|
391
|
+
});
|
|
392
|
+
|
|
362
393
|
test("a met quorum with an open concern ratifies, and the concern stays listed", async () => {
|
|
363
394
|
await agree("alice");
|
|
364
395
|
await agree("bob");
|
|
@@ -379,7 +410,7 @@ describe("ratifying against the quorum (#2873)", () => {
|
|
|
379
410
|
await agree("alice");
|
|
380
411
|
const digest = recordTextDigest(text("ws-001-one.md"));
|
|
381
412
|
const reviews = [...(data("ws-001-one.md").reviews as Data[]), { reviewer: "bob", verdict: "agree", on: "2026-09-26", digest }];
|
|
382
|
-
expect(await amendRecord({ kind: KIND, id: "ws-001", fields: fields({ state: "ratified", reviews }), cwd: dir })).toMatchObject({ changed: ["state", "reviews"] });
|
|
413
|
+
expect(await amendRecord({ kind: KIND, id: "ws-001", fields: fields({ state: "ratified", reviews }), cwd: dir })).toMatchObject({ changed: ["state", "reviews", "closed_digest"] });
|
|
383
414
|
put("ws-006-six.md", decision({ id: "ws-006", title: "Six" }));
|
|
384
415
|
await agree("alice", "ws-006");
|
|
385
416
|
await agree("bob", "ws-006");
|
|
@@ -403,11 +434,11 @@ describe("ratifying against the quorum (#2873)", () => {
|
|
|
403
434
|
expect(withRatified).toContain('reviews: { field: "reviews", decider: "decided_by", ratified: "ratified" },');
|
|
404
435
|
writeFileSync(kindFile, withRatified.replace(', ratified: "ratified" }', " }"));
|
|
405
436
|
const judged = textDigest(text("ws-001-one.md"), ["reviews", "seal"]);
|
|
406
|
-
expect(await ratify()).toMatchObject({ changed: ["state"] });
|
|
407
|
-
expect(textDigest(text("ws-001-one.md"), ["reviews", "seal"])).not.toBe(judged);
|
|
437
|
+
expect(await ratify()).toMatchObject({ changed: ["state", "closed_digest"] });
|
|
438
|
+
expect(textDigest(text("ws-001-one.md"), ["reviews", "seal", "closed_digest"])).not.toBe(judged);
|
|
408
439
|
writeFileSync(kindFile, withRatified.replace(' reviews: { field: "reviews", decider: "decided_by", ratified: "ratified" },\n', ""));
|
|
409
440
|
put("ws-006-six.md", decision({ id: "ws-006", title: "Six" }));
|
|
410
|
-
expect(await ratify("ws-006")).toMatchObject({ changed: ["state"] });
|
|
441
|
+
expect(await ratify("ws-006")).toMatchObject({ changed: ["state", "closed_digest"] });
|
|
411
442
|
});
|
|
412
443
|
|
|
413
444
|
test("a kind whose reviews.ratified is not one of its states is refused", async () => {
|
|
@@ -417,6 +448,32 @@ describe("ratifying against the quorum (#2873)", () => {
|
|
|
417
448
|
expect(code(doc)).toBe("kind-invalid");
|
|
418
449
|
expect("error" in doc && doc.error.message).toMatch(/reviews\.ratified must be one of states/);
|
|
419
450
|
});
|
|
451
|
+
|
|
452
|
+
test("on a copy of the chant repo's own decisions, verdicts count through ratifying and sealing (#2555, ws-063)", async () => {
|
|
453
|
+
for (const f of readdirSync(join(dir, "decisions"))) if (f.endsWith(".md")) rmSync(join(dir, "decisions", f));
|
|
454
|
+
for (const f of readdirSync(DECISIONS)) if (/^ws-\d{3}-.+\.md$/.test(f)) cpSync(join(DECISIONS, f), join(dir, "decisions", f));
|
|
455
|
+
const read = async () => {
|
|
456
|
+
const doc = await queryRecords({ kind: KIND, cwd: dir });
|
|
457
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
458
|
+
return doc.records;
|
|
459
|
+
};
|
|
460
|
+
const decided = (await read()).filter((r) => r.state === "decided");
|
|
461
|
+
expect(decided.length).toBeGreaterThan(50);
|
|
462
|
+
// Every sixth one, and the last, keeps the test inside the unit-test budget (#2817).
|
|
463
|
+
const sample = decided.filter((_, i) => i % 6 === 0 || i === decided.length - 1);
|
|
464
|
+
const judged = new Map(sample.map((r) => [r.id!, r.digest]));
|
|
465
|
+
for (const id of judged.keys()) {
|
|
466
|
+
await agree("alice", id);
|
|
467
|
+
await agree("Bob ", id);
|
|
468
|
+
expect(await ratify(id)).toMatchObject({ changed: ["state", "closed_digest"] });
|
|
469
|
+
}
|
|
470
|
+
for (const r of (await read()).filter((x) => judged.has(x.id!))) {
|
|
471
|
+
expect(r, r.id!).toMatchObject({ state: "ratified", valid: true, reasons: [], digest: judged.get(r.id!) });
|
|
472
|
+
expect(r.quorum, r.id!).toMatchObject({ agreed: 2, met: true, notCounted: [] });
|
|
473
|
+
expect(r.quorum!.counted.map((c) => c.principal), r.id!).toEqual(["alice", "bob"]);
|
|
474
|
+
expect(r.data?.closed_digest, r.id!).toBe(recordSeal(readFileSync(join(dir, r.path), "utf-8"), "closed_digest"));
|
|
475
|
+
}
|
|
476
|
+
});
|
|
420
477
|
});
|
|
421
478
|
|
|
422
479
|
describe("records review", () => {
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
* `records close` is in `records-close.ts`.
|
|
21
21
|
*
|
|
22
22
|
* `new --sign` and `amend --sign` seal the record's author (#2688), and
|
|
23
|
-
* `review --sign` seals a verdict (#2687): see `trust/seal.ts`.
|
|
23
|
+
* `review --sign` seals a verdict (#2687): see `trust/seal.ts`. A record of
|
|
24
|
+
* a kind that declares `seal` gets its whole-file seal when `new` or `amend`
|
|
25
|
+
* writes it into a closed state (#2546, ws-063).
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
28
|
import { readFileSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -37,6 +39,7 @@ import {
|
|
|
37
39
|
RecordReadError,
|
|
38
40
|
RECORD_SEAL_FIELD,
|
|
39
41
|
digestFields,
|
|
42
|
+
recordSeal,
|
|
40
43
|
recordTextDigest,
|
|
41
44
|
type LoadedRecordKind,
|
|
42
45
|
type RecordEntry,
|
|
@@ -47,6 +50,9 @@ import { policyAtBase, resolveBase } from "./trust/provenance";
|
|
|
47
50
|
import { WorkspaceReadError } from "./declaration";
|
|
48
51
|
import { findSessionKinds, headCommit, sessionKindsFor } from "./session-kinds";
|
|
49
52
|
import { workingTree } from "./tree";
|
|
53
|
+
import { AGENT_ENV, refuseRecordWrite, WRITE_SCOPE_CODES, WriteScopeError, type ScopeSource } from "./write-scope";
|
|
54
|
+
import { IDENTITY_CODES, IdentityError, refuseUnidentified } from "./identity";
|
|
55
|
+
import type { WriteVerb } from "./declaration";
|
|
50
56
|
|
|
51
57
|
// ── Contract ─────────────────────────────────────────────────────────────────
|
|
52
58
|
|
|
@@ -72,6 +78,8 @@ export const NEW_ERROR_CODES = [
|
|
|
72
78
|
"source-harvest-not-proposed",
|
|
73
79
|
"record-state-not-initial",
|
|
74
80
|
"ratify-quorum-not-met",
|
|
81
|
+
...WRITE_SCOPE_CODES,
|
|
82
|
+
...IDENTITY_CODES,
|
|
75
83
|
...RECORD_REASON_CODES,
|
|
76
84
|
] as const satisfies readonly ReasonCode[];
|
|
77
85
|
|
|
@@ -86,6 +94,8 @@ export const AMEND_ERROR_CODES = [
|
|
|
86
94
|
"amend-supersede-instead",
|
|
87
95
|
"record-sign-failed",
|
|
88
96
|
"ratify-quorum-not-met",
|
|
97
|
+
...WRITE_SCOPE_CODES,
|
|
98
|
+
...IDENTITY_CODES,
|
|
89
99
|
...RECORD_REASON_CODES,
|
|
90
100
|
] as const satisfies readonly ReasonCode[];
|
|
91
101
|
|
|
@@ -100,6 +110,8 @@ export const REVIEW_ERROR_CODES = [
|
|
|
100
110
|
"review-sign-failed",
|
|
101
111
|
"session-unknown",
|
|
102
112
|
"session-not-open",
|
|
113
|
+
...WRITE_SCOPE_CODES,
|
|
114
|
+
...IDENTITY_CODES,
|
|
103
115
|
...RECORD_REASON_CODES,
|
|
104
116
|
] as const satisfies readonly ReasonCode[];
|
|
105
117
|
|
|
@@ -348,6 +360,31 @@ export async function open(kind: string, cwd: string): Promise<Opened> {
|
|
|
348
360
|
};
|
|
349
361
|
}
|
|
350
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Refuse a write outside the writer's scope (#2548): the kind `o` opened,
|
|
365
|
+
* written with `verb`, by the session `agent` names (`CHANT_AGENT`) or the
|
|
366
|
+
* principal `by` names. Throws a {@link WriteScopeError}.
|
|
367
|
+
*/
|
|
368
|
+
export function refuseOutOfScope(o: Opened, verb: WriteVerb, cwd: string, writer: { agent?: string; by?: string }): ScopeSource {
|
|
369
|
+
const source = refuseRecordWrite(cwd, { kindName: o.loaded.kind.name, kindFile: o.loaded.file, recordsDir: o.loaded.dir, verb, agent: writer.agent, principal: writer.by });
|
|
370
|
+
// #3163: under identity.attribution "identified" at base, --by names a forge identity or a signer.
|
|
371
|
+
if (writer.by !== undefined) refuseUnidentified(source, [writer.by], "--by", { agent: writer.agent });
|
|
372
|
+
return source;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Refuse author fields (the kind's reviews.decider and proposedBy fields)
|
|
377
|
+
* that name a person by a bare name, under identity.attribution
|
|
378
|
+
* "identified" at base (#3163). `flag` is where the fields came from.
|
|
379
|
+
*/
|
|
380
|
+
function refuseUnidentifiedAuthors(source: ScopeSource, kind: Opened["loaded"]["kind"], fields: Record<string, unknown>, flag: string, agent?: string): void {
|
|
381
|
+
const names = [kind.reviews?.decider, kind.proposedBy?.field]
|
|
382
|
+
.filter((f): f is string => f !== undefined)
|
|
383
|
+
.map((f) => fields[f])
|
|
384
|
+
.filter((v): v is string => typeof v === "string" && v.trim() !== "");
|
|
385
|
+
refuseUnidentified(source, names, `${flag} author`, { agent });
|
|
386
|
+
}
|
|
387
|
+
|
|
351
388
|
/**
|
|
352
389
|
* The kind's records as `records` reads them from `source`. A session kind's
|
|
353
390
|
* subject records are read from the same source (#2693), so a write is
|
|
@@ -460,6 +497,33 @@ function refuseSealField(o: Opened, fields: Record<string, unknown>, flag: strin
|
|
|
460
497
|
if (o.loaded.kind.reviews && RECORD_SEAL_FIELD in fields) {
|
|
461
498
|
throw new RecordWriteError("write-input-invalid", `the fields given with ${flag} set ${RECORD_SEAL_FIELD}, and only --sign writes a record's seal`);
|
|
462
499
|
}
|
|
500
|
+
const closed = o.loaded.kind.seal?.field;
|
|
501
|
+
if (closed !== undefined && !o.loaded.kind.session && closed in fields) {
|
|
502
|
+
throw new RecordWriteError("write-input-invalid", `the fields given with ${flag} set ${closed}, and chant writes it when the record closes`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* `text`, the record `data` holds, with its whole-file seal written into the
|
|
508
|
+
* kind's `seal.field` when the record is in a closed state (#2546, ws-063).
|
|
509
|
+
* The seal goes last, after the author seal, so it covers every other value
|
|
510
|
+
* and the body. `text` is returned as it is for a kind without `seal`, a
|
|
511
|
+
* session kind (whose seal `records close` writes), or an open record.
|
|
512
|
+
*/
|
|
513
|
+
function sealClosed(o: Opened, text: string, data: Record<string, unknown>, id: string): { text: string; data: Record<string, unknown> } {
|
|
514
|
+
const { kind } = o.loaded;
|
|
515
|
+
if (!kind.seal || kind.session || kind.stateField === undefined) return { text, data };
|
|
516
|
+
const state = data[kind.stateField];
|
|
517
|
+
if (typeof state !== "string" || !(kind.closedStates ?? []).includes(state)) return { text, data };
|
|
518
|
+
const field = kind.seal.field;
|
|
519
|
+
const seal = recordSeal(text, field, kind.format);
|
|
520
|
+
if (seal === null) throw new RecordWriteError("record-unparseable", `${id} can't be read, so it can't be sealed`);
|
|
521
|
+
const sealedData = { ...data, [field]: seal };
|
|
522
|
+
const sealed = replaceFields(text, { [field]: seal }, sealedData);
|
|
523
|
+
if (sealed === undefined || recordSeal(sealed, field, kind.format) !== seal) {
|
|
524
|
+
throw new RecordWriteError("record-unparseable", `the ${field} field can't be written into ${id} without changing the text it seals`);
|
|
525
|
+
}
|
|
526
|
+
return { text: sealed, data: sealedData };
|
|
463
527
|
}
|
|
464
528
|
|
|
465
529
|
/**
|
|
@@ -584,7 +648,7 @@ function today(): string {
|
|
|
584
648
|
}
|
|
585
649
|
|
|
586
650
|
export function failure<C>(schema: string, err: unknown): WriteFailure<C> {
|
|
587
|
-
if (err instanceof RecordWriteError || err instanceof RecordReadError) {
|
|
651
|
+
if (err instanceof RecordWriteError || err instanceof RecordReadError || err instanceof WriteScopeError || err instanceof IdentityError) {
|
|
588
652
|
return { $schema: schema, contract: RECORDS_WRITE_CONTRACT_VERSION, error: { code: err.code as C, message: err.message } };
|
|
589
653
|
}
|
|
590
654
|
throw err;
|
|
@@ -644,6 +708,8 @@ export interface NewRecordOptions {
|
|
|
644
708
|
* `reviews.decider` field, which the fields must set.
|
|
645
709
|
*/
|
|
646
710
|
sign?: string | true;
|
|
711
|
+
/** The agent session the write is made in (`CHANT_AGENT`, #2548), which its write scope binds. */
|
|
712
|
+
agent?: string;
|
|
647
713
|
}
|
|
648
714
|
|
|
649
715
|
/**
|
|
@@ -727,8 +793,10 @@ export async function newRecord(opts: NewRecordOptions & ChannelOptions): Promis
|
|
|
727
793
|
throw new RecordWriteError("write-usage-invalid", `--prefix takes letters and digits, starting with a letter, not ${JSON.stringify(opts.prefix)}`);
|
|
728
794
|
}
|
|
729
795
|
const o = await open(opts.kind, opts.cwd);
|
|
796
|
+
const scope = refuseOutOfScope(o, "new", opts.cwd, opts);
|
|
730
797
|
const fields = applyChannel(o, parseFields(opts.fields, "--from"), opts, true, "--from");
|
|
731
798
|
const { kind, schema } = o.loaded;
|
|
799
|
+
refuseUnidentifiedAuthors(scope, kind, fields, "--from", opts.agent);
|
|
732
800
|
refuseSealField(o, fields, "--from");
|
|
733
801
|
refuseRevisionFields(kind, fields, {}, "--from");
|
|
734
802
|
const idField = kind.idField!;
|
|
@@ -758,6 +826,7 @@ export async function newRecord(opts: NewRecordOptions & ChannelOptions): Promis
|
|
|
758
826
|
let text = renderRecord(data, title ? `\n# ${title}\n` : "");
|
|
759
827
|
let seal: AuthorSeal | undefined;
|
|
760
828
|
if (opts.sign !== undefined) ({ text, seal } = await sealAuthor(o, text, data, id, opts.sign, opts.cwd));
|
|
829
|
+
({ text } = sealClosed(o, text, seal ? { ...data, [RECORD_SEAL_FIELD]: seal } : data, id));
|
|
761
830
|
const written = await validatedEntry(o, before, path, text);
|
|
762
831
|
await refuseRatifyBelowQuorum(o, written, id);
|
|
763
832
|
const warnings = written.warnings;
|
|
@@ -789,6 +858,8 @@ export interface AmendRecordOptions {
|
|
|
789
858
|
cwd: string;
|
|
790
859
|
/** Seal the amended record's author, as `records new` does (#2688). */
|
|
791
860
|
sign?: string | true;
|
|
861
|
+
/** The agent session the write is made in (#2548). */
|
|
862
|
+
agent?: string;
|
|
792
863
|
}
|
|
793
864
|
|
|
794
865
|
/**
|
|
@@ -808,8 +879,10 @@ export interface AmendRecordOptions {
|
|
|
808
879
|
export async function amendRecord(opts: AmendRecordOptions & ChannelOptions): Promise<AmendDocument> {
|
|
809
880
|
try {
|
|
810
881
|
const o = await open(opts.kind, opts.cwd);
|
|
882
|
+
const scope = refuseOutOfScope(o, "amend", opts.cwd, opts);
|
|
811
883
|
const given = applyChannel(o, parseFields(opts.fields, "--set"), opts, false, "--set");
|
|
812
884
|
const { kind } = o.loaded;
|
|
885
|
+
refuseUnidentifiedAuthors(scope, kind, given, "--set", opts.agent);
|
|
813
886
|
refuseSealField(o, given, "--set");
|
|
814
887
|
const before = await readAll(o, o.source);
|
|
815
888
|
const target = findRecord(before, opts.id, kind.name);
|
|
@@ -871,6 +944,12 @@ export async function amendRecord(opts: AmendRecordOptions & ChannelOptions): Pr
|
|
|
871
944
|
sealDropped = `${opts.id} was sealed${typeof signer === "string" ? ` by ${signer}` : ""}, and the amendment moves its digest or its state, so the seal was removed: seal it again with records amend ${opts.id} --sign`;
|
|
872
945
|
}
|
|
873
946
|
if (stableJson(old[RECORD_SEAL_FIELD]) !== stableJson(merged[RECORD_SEAL_FIELD])) changed.push(RECORD_SEAL_FIELD);
|
|
947
|
+
// Entering a closed state seals the whole record, last (#2546, ws-063).
|
|
948
|
+
if (changed.length > 0 && kind.seal && !kind.session) {
|
|
949
|
+
const closedSeal = sealClosed(o, text, merged, opts.id);
|
|
950
|
+
text = closedSeal.text;
|
|
951
|
+
if (stableJson(old[kind.seal.field]) !== stableJson(closedSeal.data[kind.seal.field])) changed.push(kind.seal.field);
|
|
952
|
+
}
|
|
874
953
|
let warnings = target.warnings;
|
|
875
954
|
if (changed.length > 0) {
|
|
876
955
|
const written = await validatedEntry(o, before, target.path, text);
|
|
@@ -916,6 +995,8 @@ export interface ReviewRecordOptions {
|
|
|
916
995
|
* git's `user.signingkey`. Without it the verdict is written unsealed.
|
|
917
996
|
*/
|
|
918
997
|
sign?: string | true;
|
|
998
|
+
/** The agent session the write is made in (#2548). */
|
|
999
|
+
agent?: string;
|
|
919
1000
|
}
|
|
920
1001
|
|
|
921
1002
|
/**
|
|
@@ -932,6 +1013,7 @@ export async function reviewRecord(opts: ReviewRecordOptions): Promise<ReviewDoc
|
|
|
932
1013
|
if (opts.by.trim() === "") throw new RecordWriteError("write-usage-invalid", "--by needs the reviewer's name");
|
|
933
1014
|
if (opts.session !== undefined && opts.session === "") throw new RecordWriteError("write-usage-invalid", "--session needs a session id");
|
|
934
1015
|
const o = await open(opts.kind, opts.cwd);
|
|
1016
|
+
refuseOutOfScope(o, "review", opts.cwd, opts);
|
|
935
1017
|
const { kind } = o.loaded;
|
|
936
1018
|
if (!kind.reviews) {
|
|
937
1019
|
throw new RecordWriteError("review-unsupported", `the ${kind.name} kind declares no reviews field, so its records take no review`);
|
|
@@ -946,6 +1028,8 @@ export async function reviewRecord(opts: ReviewRecordOptions): Promise<ReviewDoc
|
|
|
946
1028
|
throw new RecordWriteError("review-note-required", `a dissent needs a reason: pass --note <text> with the concern`);
|
|
947
1029
|
}
|
|
948
1030
|
const inSession = opts.session !== undefined ? await openSession(o, opts.session, opts.cwd) : undefined;
|
|
1031
|
+
// The verdict is appended to the session too, so the session's kind must be in scope as well.
|
|
1032
|
+
if (inSession) refuseOutOfScope(inSession.so, "review", opts.cwd, opts);
|
|
949
1033
|
const reviews = target.data[field] ?? [];
|
|
950
1034
|
if (!Array.isArray(reviews)) throw new RecordWriteError("record-schema-invalid", `${target.path}: ${field} is not a list`);
|
|
951
1035
|
const current = o.source.read(target.path);
|
|
@@ -1129,6 +1213,8 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
|
|
|
1129
1213
|
const { args } = ctx;
|
|
1130
1214
|
const cwd = process.cwd();
|
|
1131
1215
|
const verb = args.extraPositional;
|
|
1216
|
+
// The agent session the write is made in (#2548): set by the harness that runs the agent.
|
|
1217
|
+
const agent = process.env[AGENT_ENV] || undefined;
|
|
1132
1218
|
const print = (doc: object): number => {
|
|
1133
1219
|
console.log(JSON.stringify(doc, null, 2));
|
|
1134
1220
|
return "error" in doc ? 1 : 0;
|
|
@@ -1140,7 +1226,7 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
|
|
|
1140
1226
|
if (!id) return print(usage(RECORDS_CLOSE_SCHEMA_ID, "close needs the session's id"));
|
|
1141
1227
|
const kind = args.kind !== undefined ? resolveWriteKind(args.kind, cwd) : await declaredSessionKind(RECORDS_CLOSE_SCHEMA_ID, cwd);
|
|
1142
1228
|
if (typeof kind !== "string") return print(kind);
|
|
1143
|
-
return print(await closeRecord({ kind, id, dryRun: args.dryRun, cwd }));
|
|
1229
|
+
return print(await closeRecord({ kind, id, dryRun: args.dryRun, cwd, agent }));
|
|
1144
1230
|
}
|
|
1145
1231
|
if (verb === "new") {
|
|
1146
1232
|
const named = args.extraPositional2 ?? args.kind;
|
|
@@ -1148,7 +1234,7 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
|
|
|
1148
1234
|
if (typeof kind !== "string") return print(kind);
|
|
1149
1235
|
const input = readInput(RECORDS_NEW_SCHEMA_ID, "--from", args.migrateFrom, cwd);
|
|
1150
1236
|
if (typeof input !== "string") return print(input);
|
|
1151
|
-
return print(await newRecord({ kind, fields: input, prefix: args.prefix, by: args.by, sign: args.sign, dryRun: args.dryRun, cwd }));
|
|
1237
|
+
return print(await newRecord({ kind, fields: input, prefix: args.prefix, by: args.by, sign: args.sign, dryRun: args.dryRun, cwd, agent }));
|
|
1152
1238
|
}
|
|
1153
1239
|
const schema = verb === "amend" ? RECORDS_AMEND_SCHEMA_ID : RECORDS_REVIEW_SCHEMA_ID;
|
|
1154
1240
|
const id = args.extraPositional2;
|
|
@@ -1159,11 +1245,11 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
|
|
|
1159
1245
|
if (args.by !== undefined) return print(usage(schema, "--by is not taken by amend: set decided_by, or a kind's proposedBy field, with --set"));
|
|
1160
1246
|
const input = readInput(schema, "--set", args.set, cwd);
|
|
1161
1247
|
if (typeof input !== "string") return print(input);
|
|
1162
|
-
return print(await amendRecord({ kind, id, fields: input, sign: args.sign, dryRun: args.dryRun, cwd }));
|
|
1248
|
+
return print(await amendRecord({ kind, id, fields: input, sign: args.sign, dryRun: args.dryRun, cwd, agent }));
|
|
1163
1249
|
}
|
|
1164
1250
|
if (args.verdict === undefined) return print(usage(schema, "--verdict agree|dissent|abstain is required"));
|
|
1165
1251
|
if (args.by === undefined) return print(usage(schema, "--by <principal> is required"));
|
|
1166
1252
|
return print(
|
|
1167
|
-
await reviewRecord({ kind, id, verdict: args.verdict, by: args.by, note: args.note, session: args.session, sign: args.sign, dryRun: args.dryRun, cwd }),
|
|
1253
|
+
await reviewRecord({ kind, id, verdict: args.verdict, by: args.by, note: args.note, session: args.session, sign: args.sign, dryRun: args.dryRun, cwd, agent }),
|
|
1168
1254
|
);
|
|
1169
1255
|
}
|