@intentius/chant 0.101.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/builder.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/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 +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +11 -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 +19 -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 +55 -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-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 +8 -8
- 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 +11 -11
- 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/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 +2 -0
- 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/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 +5 -0
- package/dist/workspace/agent-cli.d.ts.map +1 -1
- package/dist/workspace/box-factory.d.ts +73 -0
- package/dist/workspace/box-factory.d.ts.map +1 -0
- 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.d.ts +5 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +10 -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 +91 -4
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +234 -5
- 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/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 +18 -4
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
- package/dist/workspace/lineage-lock.d.ts +2 -2
- package/dist/workspace/lineage-lock.d.ts.map +1 -1
- package/dist/workspace/links.d.ts.map +1 -1
- 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 +21 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.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 +27 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +1 -1
- 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 +5 -4
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +15 -0
- package/dist/workspace/records.d.ts.map +1 -1
- 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/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.map +1 -1
- package/dist/workspace/trust/rotation.d.ts.map +1 -1
- package/dist/workspace/trust/seal.d.ts +33 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/test-repo.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 +79 -19
- package/dist/workspace/write-scope.d.ts.map +1 -1
- 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/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
- 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 +18 -0
- package/src/cli/commands/import-parser-options.test.ts +133 -0
- package/src/cli/commands/import.ts +21 -3
- 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/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 +87 -3
- 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 +1 -0
- package/src/cli/main.ts +65 -11
- package/src/cli/mcp/workspace-tools.test.ts +5 -2
- package/src/cli/mcp/workspace-tools.ts +29 -2
- package/src/cli/registry.ts +11 -2
- 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/testdata/postgres-tags.json +242 -0
- package/src/config.test.ts +21 -0
- package/src/config.ts +15 -0
- 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 +55 -3
- package/src/lifecycle/gate-ledger.ts +16 -0
- package/src/lifecycle/observe.test.ts +18 -0
- package/src/lifecycle/observe.ts +6 -4
- 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/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/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 +74 -0
- package/src/op/gate-approval.ts +5 -1
- package/src/op/gate.ts +31 -5
- package/src/op/index.ts +2 -0
- 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/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
- package/src/workspace/agent-cli.ts +7 -3
- package/src/workspace/agent.schema.json +22 -0
- package/src/workspace/box-factory.test.ts +320 -0
- package/src/workspace/box-factory.ts +103 -0
- package/src/workspace/changes.schema.json +12 -20
- package/src/workspace/changes.ts +4 -1
- package/src/workspace/checks/boxes.test.ts +2 -0
- package/src/workspace/checks.ts +29 -5
- package/src/workspace/compose-graph.ts +7 -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 +234 -5
- package/src/workspace/declaration.ts +174 -10
- package/src/workspace/graph-cli.ts +5 -2
- package/src/workspace/graph.schema.json +247 -0
- package/src/workspace/identity.test.ts +196 -0
- package/src/workspace/identity.ts +186 -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.ts +44 -37
- package/src/workspace/points-write.schema.json +1 -0
- package/src/workspace/principal-classes.test.ts +175 -0
- package/src/workspace/principal-classes.ts +307 -0
- package/src/workspace/reason-codes.test.ts +11 -3
- package/src/workspace/reason-codes.ts +24 -1
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-checkout.ts +106 -0
- package/src/workspace/records-cli.ts +103 -11
- package/src/workspace/records-close.schema.json +1 -0
- package/src/workspace/records-new.schema.json +2 -0
- package/src/workspace/records-review.schema.json +2 -0
- package/src/workspace/records-uncommitted.test.ts +171 -0
- package/src/workspace/records-write.ts +28 -6
- package/src/workspace/records.schema.json +72 -1
- package/src/workspace/records.ts +27 -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/status-contract.test.ts +5 -0
- package/src/workspace/status-gates.ts +22 -8
- 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/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/seal.ts +52 -1
- 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 +2 -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 +128 -2
- package/src/workspace/write-scope.ts +170 -45
- package/src/yaml.test.ts +111 -1
- package/src/yaml.ts +141 -549
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #3146, ws-077: the factory and listing on a box block, the protected paths
|
|
3
|
+
* in writeScope, and whether a workspace is plantable.
|
|
4
|
+
*
|
|
5
|
+
* The declaration reads the fields with their defaults filled in, and a
|
|
6
|
+
* factory that names an undeclared member, or a second factory, fails the
|
|
7
|
+
* read (declaration-invalid, so check fails with WSP001). Everything is
|
|
8
|
+
* opt-in: a records-only workspace and an infra workspace that declares a
|
|
9
|
+
* factory with no box services both read and check cleanly. `status --json`
|
|
10
|
+
* and `graph --json` print the factory, the listing (with the cover's hash)
|
|
11
|
+
* and `plantable`; `check --changes` reports a write to a protected path with
|
|
12
|
+
* write-scope-protected, unless the entry's except allows the change.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { writeFileSync } from "node:fs";
|
|
17
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
18
|
+
import { cleanScratch, commitAll, contract, git, repo, writeFiles } from "./__fixtures__/contract-repo";
|
|
19
|
+
import { agentSession } from "./agent-cli";
|
|
20
|
+
import agentSchema from "./agent.schema.json";
|
|
21
|
+
import { plantability } from "./box-factory";
|
|
22
|
+
import { checkChanges, type ChangesDocument } from "./changes";
|
|
23
|
+
import changesSchema from "./changes.schema.json";
|
|
24
|
+
import { runDeclarationChecks } from "./checks";
|
|
25
|
+
import { parseDeclaration, WorkspaceReadError, type Declaration } from "./declaration";
|
|
26
|
+
import { workspaceGraph } from "./graph-cli";
|
|
27
|
+
import graphSchema from "./graph.schema.json";
|
|
28
|
+
import { workspaceStatus } from "./status";
|
|
29
|
+
import statusSchema from "./status.schema.json";
|
|
30
|
+
import { judgePath, onlyKeysChanged, protectedEntry, resolveWriter } from "./write-scope";
|
|
31
|
+
import { emptyPolicy } from "./trust/policy";
|
|
32
|
+
|
|
33
|
+
afterAll(cleanScratch);
|
|
34
|
+
|
|
35
|
+
type Json = Record<string, unknown>;
|
|
36
|
+
|
|
37
|
+
const FACTORY = {
|
|
38
|
+
builds: ["studio"],
|
|
39
|
+
check: "npm run --silent check && npm test --silent",
|
|
40
|
+
checks: "checks",
|
|
41
|
+
builders: "steward",
|
|
42
|
+
publish: { repo: "arugula-salad/studio", branchPrefix: "box/" },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const SERVICES = [{ name: "app", cmd: "node app.mjs", health: "http://127.0.0.1:5173/health" }];
|
|
46
|
+
|
|
47
|
+
function declaration(over: { box?: Json; members?: Json[]; writeScope?: Json; agents?: Json[] } = {}): Json {
|
|
48
|
+
return {
|
|
49
|
+
name: "studio",
|
|
50
|
+
schema: 1,
|
|
51
|
+
members: over.members ?? [
|
|
52
|
+
{ name: "studio", dir: ".", kind: "other", because: "the repo itself" },
|
|
53
|
+
{ name: "steward", dir: "steward", kind: "other", because: "the box's steward", box: over.box ?? { services: SERVICES, factory: FACTORY } },
|
|
54
|
+
],
|
|
55
|
+
...(over.writeScope ? { writeScope: over.writeScope } : {}),
|
|
56
|
+
...(over.agents ? { agents: over.agents } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const parse = (doc: Json): Declaration => parseDeclaration(JSON.stringify(doc, null, 2), "chant.workspace.json");
|
|
61
|
+
|
|
62
|
+
/** The error a declaration's read fails with, and the line of the file it points at. */
|
|
63
|
+
function readFailure(doc: Json): { code: string; message: string; at: string | undefined } {
|
|
64
|
+
const text = JSON.stringify(doc, null, 2);
|
|
65
|
+
try {
|
|
66
|
+
parseDeclaration(text, "chant.workspace.json");
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err instanceof WorkspaceReadError) return { code: err.code, message: err.message, at: err.location ? text.split("\n")[err.location.line - 1]?.trim() : undefined };
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
throw new Error("the declaration read");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe("the factory on a box block (#3146)", () => {
|
|
75
|
+
test("reads with its defaults filled in", () => {
|
|
76
|
+
const box = parse(declaration()).members[1].box!;
|
|
77
|
+
expect(box.factory).toEqual({
|
|
78
|
+
builds: ["studio"],
|
|
79
|
+
check: { run: "npm run --silent check && npm test --silent", kind: "test" },
|
|
80
|
+
checks: "checks",
|
|
81
|
+
builders: "steward",
|
|
82
|
+
publish: { forge: "github", repo: "arugula-salad/studio", base: null, branchPrefix: "box/", head: null },
|
|
83
|
+
pointer: "/members/1/box/factory",
|
|
84
|
+
});
|
|
85
|
+
expect(box.listing).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("a check says what it is, a publish target names a base and a fork, and the branch prefix defaults to the work branch", () => {
|
|
89
|
+
const f = parse(
|
|
90
|
+
declaration({
|
|
91
|
+
box: { factory: { builds: ["studio", "steward"], check: { run: "chant build && chant lint", kind: "lint" }, publish: { repo: "acme/infra", base: "main", head: "alex/infra" } } },
|
|
92
|
+
}),
|
|
93
|
+
).members[1].box!.factory!;
|
|
94
|
+
expect(f.builds).toEqual(["studio", "steward"]);
|
|
95
|
+
expect(f.check).toEqual({ run: "chant build && chant lint", kind: "lint" });
|
|
96
|
+
expect(f.publish).toEqual({ forge: "github", repo: "acme/infra", base: "main", branchPrefix: "chant/work/", head: "alex/infra" });
|
|
97
|
+
expect(f.checks).toBeNull();
|
|
98
|
+
expect(f.builders).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("builds must name a declared member", () => {
|
|
102
|
+
const failure = readFailure(declaration({ box: { factory: { builds: ["app"] } } }));
|
|
103
|
+
expect(failure.code).toBe("declaration-invalid");
|
|
104
|
+
expect(failure.message).toContain('the factory builds "app", which is not a declared member');
|
|
105
|
+
expect(failure.at).toBe('"app"');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("builders must name a declared member", () => {
|
|
109
|
+
const failure = readFailure(declaration({ box: { factory: { builds: ["studio"], builders: "delivery" } } }));
|
|
110
|
+
expect(failure.message).toContain('the factory\'s builders names "delivery"');
|
|
111
|
+
expect(failure.at).toBe('"builders": "delivery"');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("a workspace has one factory", () => {
|
|
115
|
+
const failure = readFailure(
|
|
116
|
+
declaration({
|
|
117
|
+
members: [
|
|
118
|
+
{ name: "a", dir: "a", kind: "other", because: "a", box: { factory: { builds: ["a"] } } },
|
|
119
|
+
{ name: "b", dir: "b", kind: "other", because: "b", box: { factory: { builds: ["b"] } } },
|
|
120
|
+
],
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
expect(failure.message).toContain("members a and b both declare a factory");
|
|
124
|
+
expect(failure.at).toBe('"factory": {');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("the schema refuses a factory without builds, an unknown check kind, a bad repo and an unknown field", () => {
|
|
128
|
+
expect(readFailure(declaration({ box: { factory: {} } })).code).toBe("declaration-invalid");
|
|
129
|
+
expect(readFailure(declaration({ box: { factory: { builds: ["studio"], check: { run: "x", kind: "smoke" } } } })).code).toBe("declaration-invalid");
|
|
130
|
+
expect(readFailure(declaration({ box: { factory: { builds: ["studio"], publish: { repo: "not a repo" } } } })).code).toBe("declaration-invalid");
|
|
131
|
+
expect(readFailure(declaration({ box: { factory: { builds: ["studio"], app: "studio" } } })).message).toContain('unknown field "app"');
|
|
132
|
+
// An x- field is still allowed, so a runtime can carry its own knobs beside chant's.
|
|
133
|
+
expect(parse(declaration({ box: { factory: { builds: ["studio"], "x-studio": { budget: 3 } } } })).members[1].box!.factory!.builds).toEqual(["studio"]);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("the listing on a box block (#3146)", () => {
|
|
138
|
+
test("reads with its defaults: listed, empty title and line, no cover", () => {
|
|
139
|
+
expect(parse(declaration({ box: { listing: {} } })).members[1].box!.listing).toEqual({ published: true, title: "", line: "", cover: null });
|
|
140
|
+
expect(parse(declaration({ box: { listing: { published: false, title: "Fern", line: "A garden planner", cover: "steward/cover.png" } } })).members[1].box!.listing).toEqual({
|
|
141
|
+
published: false,
|
|
142
|
+
title: "Fern",
|
|
143
|
+
line: "A garden planner",
|
|
144
|
+
cover: "steward/cover.png",
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a title over 60 characters, a line over 140 and a control character are refused", () => {
|
|
149
|
+
expect(readFailure(declaration({ box: { listing: { title: "x".repeat(61) } } })).code).toBe("declaration-invalid");
|
|
150
|
+
expect(readFailure(declaration({ box: { listing: { line: "x".repeat(141) } } })).code).toBe("declaration-invalid");
|
|
151
|
+
expect(readFailure(declaration({ box: { listing: { title: "two\nlines" } } })).code).toBe("declaration-invalid");
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("plantable (#3146)", () => {
|
|
156
|
+
test("exactly one member's box block declares services", () => {
|
|
157
|
+
expect(plantability(parse(declaration()))).toEqual({ plantable: true, box: "steward", reason: null });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("an infra workspace with a factory and no box services reads and checks cleanly, and isn't plantable (#3174)", async () => {
|
|
161
|
+
const doc = declaration({
|
|
162
|
+
members: [
|
|
163
|
+
{ name: "estate", dir: "estate", kind: "other", because: "terraform" },
|
|
164
|
+
{ name: "ops", dir: "ops", kind: "other", because: "the steward", box: { factory: { builds: ["estate"], check: { run: "terraform plan", kind: "plan" }, publish: { repo: "acme/estate" } } } },
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
const d = parse(doc);
|
|
168
|
+
expect(plantability(d)).toMatchObject({ plantable: false, box: null, reason: { code: "box-none" } });
|
|
169
|
+
const root = repo({ "chant.workspace.json": JSON.stringify(doc), "estate/main.tf": "", "ops/README.md": "" });
|
|
170
|
+
const { diagnostics } = await runDeclarationChecks(root);
|
|
171
|
+
expect(diagnostics.filter((x) => x.severity === "error")).toEqual([]);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("a records-only workspace isn't plantable, and two boxes with services aren't either", () => {
|
|
175
|
+
expect(plantability(parse({ name: "notes", schema: 1, members: [] }))).toMatchObject({ plantable: false, reason: { code: "box-none" } });
|
|
176
|
+
const two = declaration({
|
|
177
|
+
members: [
|
|
178
|
+
{ name: "a", dir: "a", kind: "other", because: "a", box: { services: SERVICES } },
|
|
179
|
+
{ name: "b", dir: "b", kind: "other", because: "b", box: { services: SERVICES } },
|
|
180
|
+
],
|
|
181
|
+
});
|
|
182
|
+
expect(plantability(parse(two))).toMatchObject({ plantable: false, box: null, reason: { code: "box-several", message: expect.stringContaining("a, b") } });
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("protected paths in writeScope (#3146)", () => {
|
|
187
|
+
const scope = {
|
|
188
|
+
agent: {
|
|
189
|
+
protected: ["ops", "decisions/**", { path: "chant.workspace.json", except: ["diagrams"] }],
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
const d = parse(declaration({ writeScope: scope, agents: [{ name: "builder", member: "studio" }] }));
|
|
193
|
+
const writer = resolveWriter(d, emptyPolicy(null), { agent: "builder" });
|
|
194
|
+
|
|
195
|
+
test("read in file order, with except empty for a glob", () => {
|
|
196
|
+
expect(d.writeScope!.agent!.protected).toEqual([
|
|
197
|
+
{ path: "ops", except: [] },
|
|
198
|
+
{ path: "decisions/**", except: [] },
|
|
199
|
+
{ path: "chant.workspace.json", except: ["diagrams"] },
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("an entry covers the files it matches and everything under a directory it matches", () => {
|
|
204
|
+
expect(protectedEntry(d.writeScope!.agent!.protected, "ops/factory/guard.mjs")?.path).toBe("ops");
|
|
205
|
+
expect(protectedEntry(d.writeScope!.agent!.protected, "decisions/ws-001.md")?.path).toBe("decisions/**");
|
|
206
|
+
expect(protectedEntry(d.writeScope!.agent!.protected, "opsy.md")).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("a write to a protected path is out of scope, even in the session's own member", () => {
|
|
210
|
+
expect(judgePath(d, writer, "src/server.mjs")).toEqual({ ok: true });
|
|
211
|
+
expect(judgePath(d, writer, "ops/factory/guard.mjs")).toMatchObject({ ok: false, code: "write-scope-protected", message: expect.stringContaining("writeScope.agent.protected lists ops") });
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("except lets a JSON file change only in the keys it names", () => {
|
|
215
|
+
const before = JSON.stringify({ name: "studio", diagrams: [] });
|
|
216
|
+
const onlyDiagrams = JSON.stringify({ name: "studio", diagrams: [{ name: "flow" }] });
|
|
217
|
+
const alsoName = JSON.stringify({ name: "other", diagrams: [{ name: "flow" }] });
|
|
218
|
+
expect(onlyKeysChanged(before, onlyDiagrams, ["diagrams"])).toBe(true);
|
|
219
|
+
expect(onlyKeysChanged(before, alsoName, ["diagrams"])).toBe(false);
|
|
220
|
+
expect(onlyKeysChanged(null, onlyDiagrams, ["diagrams"])).toBe(false);
|
|
221
|
+
expect(onlyKeysChanged("not json", onlyDiagrams, ["diagrams"])).toBe(false);
|
|
222
|
+
expect(judgePath(d, writer, "chant.workspace.json", { before: () => before, after: () => onlyDiagrams })).toEqual({ ok: true });
|
|
223
|
+
expect(judgePath(d, writer, "chant.workspace.json", { before: () => before, after: () => alsoName })).toMatchObject({
|
|
224
|
+
ok: false,
|
|
225
|
+
code: "write-scope-protected",
|
|
226
|
+
message: expect.stringContaining("outside the top-level keys it allows (diagrams)"),
|
|
227
|
+
});
|
|
228
|
+
// Without the change in hand, an except entry can't be satisfied.
|
|
229
|
+
expect(judgePath(d, writer, "chant.workspace.json")).toMatchObject({ ok: false, code: "write-scope-protected" });
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("the read contract: status, graph, agent and check --changes (#3146)", () => {
|
|
234
|
+
const status = contract(statusSchema);
|
|
235
|
+
const graph = contract(graphSchema);
|
|
236
|
+
const agent = contract(agentSchema);
|
|
237
|
+
const changes = contract(changesSchema);
|
|
238
|
+
const cover = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 1, 2, 3]);
|
|
239
|
+
const doc = declaration({
|
|
240
|
+
box: { services: SERVICES, factory: FACTORY, listing: { title: "Studio", line: "Where boxes are planted", cover: "steward/cover.png" } },
|
|
241
|
+
writeScope: { agent: { protected: ["ops", { path: "chant.workspace.json", except: ["diagrams"] }] } },
|
|
242
|
+
agents: [{ name: "builder", member: "studio" }],
|
|
243
|
+
});
|
|
244
|
+
let root: string;
|
|
245
|
+
|
|
246
|
+
beforeAll(() => {
|
|
247
|
+
root = repo({ "chant.workspace.json": `${JSON.stringify(doc, null, 2)}\n`, "steward/README.md": "the steward\n", "ops/guard.mjs": "export {};\n", "server.mjs": "export {};\n" });
|
|
248
|
+
writeFileSync(`${root}/steward/cover.png`, cover);
|
|
249
|
+
git(root, "add", "-A");
|
|
250
|
+
git(root, "commit", "-q", "-m", "the workspace");
|
|
251
|
+
git(root, "branch", "-M", "main");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("status --json prints the factory, the listing with the cover's hash, and plantable", async () => {
|
|
255
|
+
const out = await workspaceStatus({ cwd: root, env: "prod" });
|
|
256
|
+
status.expectValid(out);
|
|
257
|
+
if ("error" in out) throw new Error(out.error.message);
|
|
258
|
+
expect(out.plantable).toEqual({ plantable: true, box: "steward", reason: null });
|
|
259
|
+
const box = out.members.find((m) => m.name === "steward")!.box!;
|
|
260
|
+
expect(box.factory).toEqual({
|
|
261
|
+
builds: ["studio"],
|
|
262
|
+
check: { run: "npm run --silent check && npm test --silent", kind: "test" },
|
|
263
|
+
checks: "checks",
|
|
264
|
+
builders: "steward",
|
|
265
|
+
publish: { forge: "github", repo: "arugula-salad/studio", base: null, branchPrefix: "box/", head: null },
|
|
266
|
+
});
|
|
267
|
+
expect(box.listing).toEqual({
|
|
268
|
+
published: true,
|
|
269
|
+
title: "Studio",
|
|
270
|
+
line: "Where boxes are planted",
|
|
271
|
+
cover: { path: "steward/cover.png", sha256: createHash("sha256").update(cover).digest("hex") },
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("graph --json prints them too, at a revision with --at", async () => {
|
|
276
|
+
const head = git(root, "rev-parse", "HEAD");
|
|
277
|
+
for (const at of [undefined, head]) {
|
|
278
|
+
const { doc: out } = await workspaceGraph({ cwd: root, at });
|
|
279
|
+
graph.expectValid(out);
|
|
280
|
+
if ("error" in out) throw new Error(out.error.message);
|
|
281
|
+
expect(out.plantable).toMatchObject({ plantable: true, box: "steward" });
|
|
282
|
+
const byName = Object.fromEntries(out.members.map((m) => [m.name, m.box]));
|
|
283
|
+
expect(byName.studio).toBeNull();
|
|
284
|
+
expect(byName.steward?.factory?.builds).toEqual(["studio"]);
|
|
285
|
+
expect(byName.steward?.listing?.cover?.sha256).toBe(createHash("sha256").update(cover).digest("hex"));
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("workspace agent lists the protected paths", async () => {
|
|
290
|
+
const out = await agentSession({ cwd: root, name: "builder" });
|
|
291
|
+
agent.expectValid(out);
|
|
292
|
+
if ("error" in out) throw new Error(out.error.message);
|
|
293
|
+
expect(out.scope.protected).toEqual([
|
|
294
|
+
{ path: "ops", except: [] },
|
|
295
|
+
{ path: "chant.workspace.json", except: ["diagrams"] },
|
|
296
|
+
]);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("check --changes reports a write to a protected path, and lets an excepted key change", async () => {
|
|
300
|
+
git(root, "checkout", "-q", "-b", "work");
|
|
301
|
+
const trailer = "\n\nChant-Agent: builder";
|
|
302
|
+
writeFiles(root, { "server.mjs": "export const port = 1;\n" });
|
|
303
|
+
const ok = commitAll(root, `app: a port${trailer}`);
|
|
304
|
+
writeFiles(root, { "chant.workspace.json": `${JSON.stringify({ ...doc, diagrams: [] }, null, 2)}\n` });
|
|
305
|
+
const excepted = commitAll(root, `a diagram${trailer}`);
|
|
306
|
+
writeFiles(root, { "ops/guard.mjs": "export const off = true;\n" });
|
|
307
|
+
const guard = commitAll(root, `the steward${trailer}`);
|
|
308
|
+
writeFiles(root, { "chant.workspace.json": `${JSON.stringify({ ...doc, name: "renamed", diagrams: [] }, null, 2)}\n` });
|
|
309
|
+
const renamed = commitAll(root, `rename${trailer}`);
|
|
310
|
+
const { doc: out } = (await checkChanges({ cwd: root, range: "main..work" })) as { doc: Exclude<ChangesDocument, { error: unknown }> };
|
|
311
|
+
changes.expectValid(out);
|
|
312
|
+
const found = out.scope!.findings.map((f) => [f.commit, f.code, f.path]);
|
|
313
|
+
expect(found).toEqual([
|
|
314
|
+
[guard, "write-scope-protected", "ops/guard.mjs"],
|
|
315
|
+
[renamed, "write-scope-protected", "chant.workspace.json"],
|
|
316
|
+
]);
|
|
317
|
+
expect(found.map(([c]) => c)).not.toContain(ok);
|
|
318
|
+
expect(found.map(([c]) => c)).not.toContain(excepted);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The factory and listing on a box block, and whether a workspace is
|
|
3
|
+
* plantable (#3146, ws-077), as `chant workspace status --json` and
|
|
4
|
+
* `chant workspace graph --json` print them.
|
|
5
|
+
*
|
|
6
|
+
* Every field here is opt-in (#3174): a workspace with no box, or a box with
|
|
7
|
+
* no `factory` or `listing`, reads as `null` for each, and plantability is a
|
|
8
|
+
* fact about the declaration, never a check that fails. A records-only
|
|
9
|
+
* workspace and an infra workspace with a factory and no box services both
|
|
10
|
+
* read and check cleanly; they just aren't plantable.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import type { BoxFactory, BoxListing, Declaration, FactoryCheckKind } from "./declaration";
|
|
15
|
+
import type { ReasonCode } from "./reason-codes";
|
|
16
|
+
import type { WorkspaceTree } from "./tree";
|
|
17
|
+
|
|
18
|
+
/** Why a workspace isn't plantable. Closed. */
|
|
19
|
+
export const PLANTABLE_REASON_CODES = ["box-none", "box-several"] as const satisfies readonly ReasonCode[];
|
|
20
|
+
export type PlantableReasonCode = (typeof PLANTABLE_REASON_CODES)[number];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether a host such as a studio's planter can plant the workspace as one
|
|
24
|
+
* box: exactly one member's box block declares services. `box` names that
|
|
25
|
+
* member, and is null when the workspace isn't plantable.
|
|
26
|
+
*/
|
|
27
|
+
export interface Plantable {
|
|
28
|
+
plantable: boolean;
|
|
29
|
+
box: string | null;
|
|
30
|
+
reason: { code: PlantableReasonCode; message: string } | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A box's factory as the read contract prints it: every field present, defaults filled in. */
|
|
34
|
+
export interface FactoryView {
|
|
35
|
+
builds: string[];
|
|
36
|
+
check: { run: string; kind: FactoryCheckKind } | null;
|
|
37
|
+
checks: string | null;
|
|
38
|
+
builders: string | null;
|
|
39
|
+
publish: { forge: "github"; repo: string; base: string | null; branchPrefix: string; head: string | null } | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A box's listing as the read contract prints it, with the cover's hash when the file is there. */
|
|
43
|
+
export interface ListingView {
|
|
44
|
+
published: boolean;
|
|
45
|
+
title: string;
|
|
46
|
+
line: string;
|
|
47
|
+
/** The cover file from the workspace root and the sha256 of its bytes, null when the file can't be read; null when none is declared. */
|
|
48
|
+
cover: { path: string; sha256: string | null } | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Whether the declaration is plantable (#3146): exactly one member's box block declares services. */
|
|
52
|
+
export function plantability(declaration: Declaration): Plantable {
|
|
53
|
+
const boxes = declaration.members.filter((m) => (m.box?.services.length ?? 0) > 0);
|
|
54
|
+
if (boxes.length === 1) return { plantable: true, box: boxes[0].name, reason: null };
|
|
55
|
+
if (boxes.length === 0) {
|
|
56
|
+
return { plantable: false, box: null, reason: { code: "box-none", message: "no member's box block declares services, so there is no box to plant" } };
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
plantable: false,
|
|
60
|
+
box: null,
|
|
61
|
+
reason: { code: "box-several", message: `members ${boxes.map((m) => m.name).join(", ")} each declare box services, and a planted workspace runs one box` },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function factoryView(f: BoxFactory | null): FactoryView | null {
|
|
66
|
+
if (f === null) return null;
|
|
67
|
+
return {
|
|
68
|
+
builds: [...f.builds],
|
|
69
|
+
check: f.check === null ? null : { ...f.check },
|
|
70
|
+
checks: f.checks,
|
|
71
|
+
builders: f.builders,
|
|
72
|
+
publish: f.publish === null ? null : { ...f.publish },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The listing, hashing the cover through `read` (bytes, or null when the file can't be read). */
|
|
77
|
+
export function listingView(l: BoxListing | null, read: (path: string) => Uint8Array | null): ListingView | null {
|
|
78
|
+
if (l === null) return null;
|
|
79
|
+
let cover: ListingView["cover"] = null;
|
|
80
|
+
if (l.cover !== null) {
|
|
81
|
+
const bytes = read(l.cover);
|
|
82
|
+
cover = { path: l.cover, sha256: bytes === null ? null : createHash("sha256").update(bytes).digest("hex") };
|
|
83
|
+
}
|
|
84
|
+
return { published: l.published, title: l.title, line: l.line, cover };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A reader of a file's bytes in `tree` for {@link listingView}: null when the file can't be read. */
|
|
88
|
+
export function treeBytes(tree: WorkspaceTree): (path: string) => Uint8Array | null {
|
|
89
|
+
return (path) => {
|
|
90
|
+
try {
|
|
91
|
+
if (tree.stat(path) !== "file") return null;
|
|
92
|
+
return tree.bytes ? tree.bytes(path) : new TextEncoder().encode(tree.read(path));
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The box fields the graph prints on a member (#3146): its factory and listing, or null when the member's box declares neither. */
|
|
100
|
+
export function graphBox(box: { factory: BoxFactory | null; listing: BoxListing | null } | null, tree: WorkspaceTree): { factory: FactoryView | null; listing: ListingView | null } | null {
|
|
101
|
+
if (box === null || (box.factory === null && box.listing === null)) return null;
|
|
102
|
+
return { factory: factoryView(box.factory), listing: listingView(box.listing, treeBytes(tree)) };
|
|
103
|
+
}
|
|
@@ -498,14 +498,9 @@
|
|
|
498
498
|
"restricted": {
|
|
499
499
|
"type": "array",
|
|
500
500
|
"items": {
|
|
501
|
-
"
|
|
502
|
-
"human",
|
|
503
|
-
"agent",
|
|
504
|
-
"runner",
|
|
505
|
-
"service"
|
|
506
|
-
]
|
|
501
|
+
"$ref": "#/$defs/principalClass"
|
|
507
502
|
},
|
|
508
|
-
"description": "The principal classes with a writeScope entry, and agent when any agent session is declared."
|
|
503
|
+
"description": "The principal classes with a writeScope entry, and agent when any agent session is declared: the core classes first, then domain classes in pin order, then any class writeScope names that no pinned package supplies."
|
|
509
504
|
},
|
|
510
505
|
"agents": {
|
|
511
506
|
"type": "array",
|
|
@@ -528,6 +523,11 @@
|
|
|
528
523
|
}
|
|
529
524
|
}
|
|
530
525
|
},
|
|
526
|
+
"principalClass": {
|
|
527
|
+
"type": "string",
|
|
528
|
+
"pattern": "^(?!x-)[a-z][a-z0-9-]{0,39}$",
|
|
529
|
+
"description": "A principal class: human, agent, runner or service, or a domain class a pinned package supplies at its ./workspace-principals subpath (#3080). A reader that knows only the core classes treats any other name as a domain class."
|
|
530
|
+
},
|
|
531
531
|
"scopeCommit": {
|
|
532
532
|
"type": "object",
|
|
533
533
|
"required": [
|
|
@@ -556,12 +556,7 @@
|
|
|
556
556
|
"description": "Whether principal is an attested signer."
|
|
557
557
|
},
|
|
558
558
|
"class": {
|
|
559
|
-
"
|
|
560
|
-
"human",
|
|
561
|
-
"agent",
|
|
562
|
-
"runner",
|
|
563
|
-
"service"
|
|
564
|
-
],
|
|
559
|
+
"$ref": "#/$defs/principalClass",
|
|
565
560
|
"description": "agent for a commit naming a session with a Chant-Agent trailer or by a principal a session lists; otherwise the class the principal's role grants at base give."
|
|
566
561
|
},
|
|
567
562
|
"agent": {
|
|
@@ -599,6 +594,8 @@
|
|
|
599
594
|
"enum": [
|
|
600
595
|
"write-scope-member",
|
|
601
596
|
"write-scope-kind",
|
|
597
|
+
"write-scope-class-unknown",
|
|
598
|
+
"write-scope-protected",
|
|
602
599
|
"agent-unknown"
|
|
603
600
|
]
|
|
604
601
|
},
|
|
@@ -611,18 +608,13 @@
|
|
|
611
608
|
"string",
|
|
612
609
|
"null"
|
|
613
610
|
],
|
|
614
|
-
"description": "From the workspace root; null for agent-unknown,
|
|
611
|
+
"description": "From the workspace root; null for agent-unknown and write-scope-class-unknown, findings on the whole commit."
|
|
615
612
|
},
|
|
616
613
|
"principal": {
|
|
617
614
|
"type": "string"
|
|
618
615
|
},
|
|
619
616
|
"class": {
|
|
620
|
-
"
|
|
621
|
-
"human",
|
|
622
|
-
"agent",
|
|
623
|
-
"runner",
|
|
624
|
-
"service"
|
|
625
|
-
]
|
|
617
|
+
"$ref": "#/$defs/principalClass"
|
|
626
618
|
},
|
|
627
619
|
"agent": {
|
|
628
620
|
"type": [
|
package/src/workspace/changes.ts
CHANGED
|
@@ -44,6 +44,7 @@ import { declaredRecordKinds, readDeclaration, readerVersion, WORKSPACE_ERROR_CO
|
|
|
44
44
|
import { declaredKindFile } from "./declared-kinds";
|
|
45
45
|
import { isGeneratedPath } from "./generated-files";
|
|
46
46
|
import { parseRegion } from "./intent";
|
|
47
|
+
import { loadClassRegistry } from "./principal-classes";
|
|
47
48
|
import { constraintCovers, isWorkspacePath, memberHolding } from "./record-assets";
|
|
48
49
|
import { loadRecordKind, RecordReadError, type LoadedRecordKind, type RecordEntry } from "./records";
|
|
49
50
|
import { readRecordsFor } from "./records-cli";
|
|
@@ -529,7 +530,9 @@ async function writeScopeAtBase(query: ChangesQuery, top: string, range: { base:
|
|
|
529
530
|
format: kind.format,
|
|
530
531
|
});
|
|
531
532
|
}
|
|
532
|
-
|
|
533
|
+
// The principal classes the pins supply, a path pin read at the base too (#3080).
|
|
534
|
+
const classes = loadClassRegistry(declaration.pins, located.rootOnDisk, { tree: located.tree }).registry;
|
|
535
|
+
return checkWriteScope({ top, base: range.base, head: range.head, prefix, declaration, kinds, classes });
|
|
533
536
|
}
|
|
534
537
|
|
|
535
538
|
function finding(code: ChangesFindingCode, p: ChangedPath, severity: "warn" | "fail", message: string, records: string[]): ChangeFinding {
|
|
@@ -158,6 +158,8 @@ describe("the box block in the declaration", () => {
|
|
|
158
158
|
isolation: null,
|
|
159
159
|
intent: null,
|
|
160
160
|
services: [],
|
|
161
|
+
factory: null,
|
|
162
|
+
listing: null,
|
|
161
163
|
capabilities: [
|
|
162
164
|
{ name: "fountain", broker: "lobby", scope: ["agent", "vault"], pointer: "/members/0/box/capabilities/0" },
|
|
163
165
|
{ name: "inference", broker: null, scope: [], pointer: "/members/0/box/capabilities/1" },
|
package/src/workspace/checks.ts
CHANGED
|
@@ -46,6 +46,7 @@ import { readDeclaration, resolveGroups, WorkspaceReadError, type Declaration, t
|
|
|
46
46
|
import type { ReasonCode } from "./reason-codes";
|
|
47
47
|
import { parseJsonText, pointerToken, type TextLocation } from "./jsonc";
|
|
48
48
|
import { loadKindRegistry, probeKind, resolveKind, type KindLoadProblem, type KindRegistry } from "./kinds";
|
|
49
|
+
import { coreClassRegistry, loadClassRegistry, unknownScopeClasses, type ClassRegistry } from "./principal-classes";
|
|
49
50
|
import { gitTop, workingTree, type WorkspaceTree } from "./tree";
|
|
50
51
|
import { LINK_CHECKS, linkTable } from "./checks/links";
|
|
51
52
|
import type { LinkRow, LinkTableRow } from "./links";
|
|
@@ -89,6 +90,10 @@ export interface WorkspaceCheckContext {
|
|
|
89
90
|
kinds: KindRegistry;
|
|
90
91
|
/** Why some pinned kinds could not be read. */
|
|
91
92
|
kindProblems: KindLoadProblem[];
|
|
93
|
+
/** The principal classes: the core four and those the pins supply (#3080). Without it, the core four. */
|
|
94
|
+
classes?: ClassRegistry;
|
|
95
|
+
/** Why some pinned principal classes could not be read (#3080). */
|
|
96
|
+
classProblems?: KindLoadProblem[];
|
|
92
97
|
/** The facts gathered before the checks ran. */
|
|
93
98
|
facts?: WorkspaceFacts;
|
|
94
99
|
}
|
|
@@ -182,23 +187,41 @@ export const WORKSPACE_CHECKS: readonly WorkspaceCheck[] = [
|
|
|
182
187
|
{
|
|
183
188
|
id: "WSP002",
|
|
184
189
|
name: "kinds-unreadable",
|
|
185
|
-
description: "Every pinned package's kinds can be read: it is installed at the pinned version,
|
|
190
|
+
description: "Every pinned package's kinds and principal classes can be read: it is installed at the pinned version, its ./workspace-kinds file is valid kind data, and its ./workspace-principals file is valid class data.",
|
|
186
191
|
severity: "error",
|
|
187
192
|
configurable: false,
|
|
188
193
|
check(ctx) {
|
|
189
|
-
|
|
194
|
+
// A pin that can't be read is a problem for both loaders; report it once.
|
|
195
|
+
const seen = new Set<string>();
|
|
196
|
+
return [...ctx.kindProblems, ...(ctx.classProblems ?? [])]
|
|
197
|
+
.filter((p) => {
|
|
198
|
+
const key = `${p.pin}\0${p.message}`;
|
|
199
|
+
if (seen.has(key)) return false;
|
|
200
|
+
seen.add(key);
|
|
201
|
+
return true;
|
|
202
|
+
})
|
|
203
|
+
.map((p) => ({ checkId: this.id, severity: this.severity, message: p.message, pointer: `/pins/${p.pin}` }));
|
|
190
204
|
},
|
|
191
205
|
},
|
|
192
206
|
{
|
|
193
207
|
id: "WSP003",
|
|
194
208
|
name: "kind-unknown",
|
|
195
|
-
description: "Every member's kind is built in or supplied by a pinned package. Unknown kinds fail closed.",
|
|
209
|
+
description: "Every member's kind is built in or supplied by a pinned package, and so is every principal class writeScope names (#3080). Unknown kinds and classes fail closed.",
|
|
196
210
|
severity: "error",
|
|
197
211
|
configurable: false,
|
|
198
212
|
check(ctx) {
|
|
199
|
-
|
|
213
|
+
const members = ctx.declaration.members
|
|
200
214
|
.filter((m) => ctx.kinds.get(m.kind) === undefined)
|
|
201
215
|
.map((m) => entryFinding(this, m, "kind", `member ${m.name} has kind ${m.kind}, which no built-in kind or pinned package supplies; known kinds: ${ctx.kinds.names().join(", ")}`));
|
|
216
|
+
const classes = ctx.classes ?? coreClassRegistry();
|
|
217
|
+
const scope = unknownScopeClasses(ctx.declaration.writeScope, classes).map((c) => ({
|
|
218
|
+
checkId: this.id,
|
|
219
|
+
severity: this.severity,
|
|
220
|
+
message: `writeScope names the principal class ${c}, which no core class or pinned package supplies, so a writer judged human is refused with write-scope-class-unknown; known classes: ${classes.names().join(", ")}`,
|
|
221
|
+
entity: c,
|
|
222
|
+
pointer: `/writeScope/${pointerToken(c)}`,
|
|
223
|
+
}));
|
|
224
|
+
return [...members, ...scope];
|
|
202
225
|
},
|
|
203
226
|
},
|
|
204
227
|
{
|
|
@@ -484,6 +507,7 @@ export async function runDeclarationChecks(
|
|
|
484
507
|
return { file: display(location.file), diagnostics: [diagnostic], suppressed: [], links: [], ok: false };
|
|
485
508
|
}
|
|
486
509
|
const { registry, problems } = loadKindRegistry(declaration.pins, root);
|
|
510
|
+
const classes = loadClassRegistry(declaration.pins, root, { tree });
|
|
487
511
|
const gathered = options.gather === false || options.tree ? {} : await gatherWorkspaceFacts(root, declaration, options);
|
|
488
512
|
// The declared record kinds load from the working tree; under --at only whether each exists at the revision is checked (#2680).
|
|
489
513
|
// A declared work kind's acceptance criteria are counted in the working tree too (#2772).
|
|
@@ -497,7 +521,7 @@ export async function runDeclarationChecks(
|
|
|
497
521
|
...(boxIntents ? { boxIntents } : {}),
|
|
498
522
|
...(options.live && !options.tree ? { live: await gatherLiveLinkFacts(root, declaration, registry, options.live.env, options.live.onStderr) } : {}),
|
|
499
523
|
};
|
|
500
|
-
const ctx: WorkspaceCheckContext = { declaration, tree, groups, kinds: registry, kindProblems: problems, facts };
|
|
524
|
+
const ctx: WorkspaceCheckContext = { declaration, tree, groups, kinds: registry, kindProblems: problems, classes: classes.registry, classProblems: classes.problems, facts };
|
|
501
525
|
const findings = runWorkspaceChecks(ctx);
|
|
502
526
|
const { active, suppressed } = applyCheckSettings(declaration, findings);
|
|
503
527
|
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* `chant graph` itself is untouched: this is the only place members compose.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
+
import type { FactoryView, ListingView } from "./box-factory";
|
|
32
33
|
import type { Declaration } from "./declaration";
|
|
33
34
|
import type { KindRegistry } from "./kinds";
|
|
34
35
|
import { graphLinks, type LinkTableRow } from "./links";
|
|
@@ -93,6 +94,12 @@ export interface ComposedMember {
|
|
|
93
94
|
* the outer `nodes` with `outer/inner/id` ids.
|
|
94
95
|
*/
|
|
95
96
|
nested?: NestedWorkspace;
|
|
97
|
+
/**
|
|
98
|
+
* The factory and listing the member's box block declares (#3146, ws-077),
|
|
99
|
+
* read from the declaration at the revision read. Set by
|
|
100
|
+
* `chant workspace graph` on every member; null when the box declares neither.
|
|
101
|
+
*/
|
|
102
|
+
box?: { factory: FactoryView | null; listing: ListingView | null } | null;
|
|
96
103
|
/** Whole-read facts the member's IR carried (`meta`, `pipeline`), kept apart from the composed sections. */
|
|
97
104
|
meta?: Record<string, unknown>;
|
|
98
105
|
pipeline?: unknown;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// The answers to the workspace's decision points (ws-058, #2739), data only:
|
|
2
|
+
// no imports, no code.
|
|
3
|
+
//
|
|
4
|
+
// chant workspace points --open --json
|
|
5
|
+
// chant workspace points ask slice-tier --inputs inputs.json --subject W-002
|
|
6
|
+
// chant workspace points answer <id> --answer medium --by <name>
|
|
7
|
+
//
|
|
8
|
+
// The points are declared in ../decisions/points.json: typed questions, the
|
|
9
|
+
// read-contract outputs they read, and a table, model and quorum chain. Each
|
|
10
|
+
// answer is one record here, named for the point and its inputs' hash, so the
|
|
11
|
+
// same question is answered once. A table's answer is answered, a model's is
|
|
12
|
+
// proposed until a person confirms it, and a question nobody before the
|
|
13
|
+
// quorum answered is escalated to people. answer.schema.json is a copy of the
|
|
14
|
+
// one @intentius/chant ships as workspace/point-answer.schema.json.
|
|
15
|
+
export const recordKind = {
|
|
16
|
+
name: "answer",
|
|
17
|
+
location: { dir: ".", match: "^[a-z][a-z0-9-]*-[0-9a-f]{12}\\.md$" },
|
|
18
|
+
format: "markdown-front-matter",
|
|
19
|
+
schema: { id: "urn:intentius:chant:point-answer:1", path: "answer.schema.json" },
|
|
20
|
+
idField: "id",
|
|
21
|
+
stateField: "state",
|
|
22
|
+
states: ["escalated", "proposed", "answered"],
|
|
23
|
+
closedStates: ["answered"],
|
|
24
|
+
constrains: { field: "constrains" },
|
|
25
|
+
source: { field: "source" },
|
|
26
|
+
answers: { points: "../decisions/points.json" },
|
|
27
|
+
};
|