@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
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* workspace work history <id>` (#2785): the item's lease history, each claim
|
|
5
5
|
* and how it ended, a read-contract output (`work-history.schema.json`).
|
|
6
6
|
*
|
|
7
|
+
* And `chant workspace work evidence <id>` (#3159): the `workEvidence`
|
|
8
|
+
* activity as a command, so a writer outside an Op attaches evidence under
|
|
9
|
+
* the lease it holds.
|
|
10
|
+
*
|
|
7
11
|
* The lease itself is `../lifecycle/work-lease.ts`, the operator lease under
|
|
8
12
|
* the key `work/<id>`. This file finds the work item first: in the work kind
|
|
9
13
|
* `--kind` names, or in the one work kind the declaration names that has a
|
|
@@ -26,13 +30,18 @@ import type { CommandContext } from "../cli/registry";
|
|
|
26
30
|
import { parseDuration } from "../op/duration";
|
|
27
31
|
import {
|
|
28
32
|
claimWorkLease,
|
|
33
|
+
isWorkLeaseOutcome,
|
|
29
34
|
leaseHistoryPath,
|
|
35
|
+
listKeptAttempts,
|
|
30
36
|
listWorkLeases,
|
|
37
|
+
outcomeCountsAsAttempt,
|
|
31
38
|
readLeaseHistory,
|
|
32
39
|
releaseWorkLease,
|
|
33
40
|
renewWorkLease,
|
|
34
41
|
workLeaseRef,
|
|
35
42
|
WORK_ITEM_ID_PATTERN,
|
|
43
|
+
WORK_LEASE_OUTCOMES,
|
|
44
|
+
type KeptAttempt,
|
|
36
45
|
type WorkLease,
|
|
37
46
|
type WorkLeaseRefusal,
|
|
38
47
|
type WorkLeaseResult,
|
|
@@ -53,11 +62,12 @@ export const WORK_LEASE_CONTRACT_VERSION = 1;
|
|
|
53
62
|
/** `$id` of the JSON Schema for the `--json` output, shipped beside this file. */
|
|
54
63
|
export const WORK_LEASE_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/work-lease/v1/work-lease.schema.json";
|
|
55
64
|
|
|
56
|
-
export { WORK_LEASE_REFUSALS } from "../lifecycle/work-lease";
|
|
65
|
+
export { WORK_LEASE_REFUSALS, WORK_LEASE_OUTCOMES } from "../lifecycle/work-lease";
|
|
57
66
|
|
|
58
67
|
const USAGE = [
|
|
59
|
-
"chant workspace work claim|renew|release <id> --holder <name> [--kind <kind file>] [--ttl <seconds|duration>] [--token <token>] [--outcome <
|
|
68
|
+
"chant workspace work claim|renew|release <id> --holder <name> [--kind <kind file>] [--ttl <seconds|duration>] [--token <token>] [--outcome <outcome>] [--note <text>] [--json]",
|
|
60
69
|
"chant workspace work history <id> [--kind <kind file>] [--json]",
|
|
70
|
+
"chant workspace work evidence <id> --holder <name> --token <token> --from <file|-> [--kind <kind file>]",
|
|
61
71
|
].join("\n");
|
|
62
72
|
|
|
63
73
|
/** Exit code when the lease was refused rather than the command failing. */
|
|
@@ -193,6 +203,9 @@ export async function workLease(req: WorkLeaseRequest): Promise<WorkLeaseDocumen
|
|
|
193
203
|
const doc = { $schema: WORK_LEASE_OUTPUT_SCHEMA_ID, contract: WORK_LEASE_CONTRACT_VERSION };
|
|
194
204
|
try {
|
|
195
205
|
if (!WORK_ITEM_ID_PATTERN.test(req.id) || req.id.includes("..")) throw new WorkError("write-usage-invalid", `${JSON.stringify(req.id)} is not a work item id`);
|
|
206
|
+
if (req.outcome !== undefined && !isWorkLeaseOutcome(req.outcome)) {
|
|
207
|
+
throw new WorkError("write-usage-invalid", `${JSON.stringify(req.outcome)} is not a lease outcome: use one of ${WORK_LEASE_OUTCOMES.join(", ")}`);
|
|
208
|
+
}
|
|
196
209
|
if (!gitRoot(req.cwd)) throw new WorkError("not-a-git-repository", "a work lease is a git ref, and this directory is not in a git repository");
|
|
197
210
|
const { loaded, state, root } = await findWorkItem(req.id, req.cwd, req.kind);
|
|
198
211
|
if (req.verb === "claim" && state !== null && (loaded.kind.closedStates ?? []).includes(state)) {
|
|
@@ -259,6 +272,27 @@ export interface LeaseClaim {
|
|
|
259
272
|
ended: ClaimEnd;
|
|
260
273
|
/** The release, when it was released. */
|
|
261
274
|
release: { by: string; at: string; outcome: string | null; note: string | null } | null;
|
|
275
|
+
/**
|
|
276
|
+
* Whether the claim counts toward the item's attempt limit (#3147): one
|
|
277
|
+
* released `not_done` or `abandoned`, with no outcome or an outcome from
|
|
278
|
+
* before the list was closed, or one that expired or was lost unreleased.
|
|
279
|
+
* Not one that is held, and not one released with any other outcome.
|
|
280
|
+
*/
|
|
281
|
+
attempt: boolean;
|
|
282
|
+
/** The ref keeping this claim's unfinished work, `refs/chant/kept/.../<item>/<token>`, or null. */
|
|
283
|
+
kept: string | null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** An item's attempts against its limit (#3147). */
|
|
287
|
+
export interface WorkAttempts {
|
|
288
|
+
/** The claims that count as attempts. */
|
|
289
|
+
failed: number;
|
|
290
|
+
/** The item's limit: its own field, else its kind's default; null when neither declares one. */
|
|
291
|
+
limit: number | null;
|
|
292
|
+
/** How many attempts are left, never below 0; null without a limit. */
|
|
293
|
+
remaining: number | null;
|
|
294
|
+
/** True once failed reaches the limit: a runner leaves the item to people. */
|
|
295
|
+
exhausted: boolean;
|
|
262
296
|
}
|
|
263
297
|
|
|
264
298
|
/** What `work history --json` prints. */
|
|
@@ -283,17 +317,21 @@ export type WorkHistoryDocument =
|
|
|
283
317
|
/** Lines of the history that are not lease events, left out. */
|
|
284
318
|
malformed: number;
|
|
285
319
|
summary: { claims: number; released: number; held: number; expired: number; lost: number; outcomes: Record<string, number> };
|
|
320
|
+
/** The item's attempts against its limit (#3147). */
|
|
321
|
+
attempts: WorkAttempts;
|
|
322
|
+
/** Every kept attempt of the item, by ref (#3147). */
|
|
323
|
+
kept: KeptAttempt[];
|
|
286
324
|
}
|
|
287
325
|
| { $schema: string; contract: number; chant: string; error: { code: WorkHistoryErrorCode; message: string } };
|
|
288
326
|
|
|
289
327
|
/** Fold a history into its claims: one per token, in the order they were first seen. */
|
|
290
|
-
export function claimsOf(events: LeaseHistoryRecord[], live: Pick<WorkLeaseState, "token" | "state"> | null): LeaseClaim[] {
|
|
328
|
+
export function claimsOf(events: LeaseHistoryRecord[], live: Pick<WorkLeaseState, "token" | "state"> | null, kept: readonly KeptAttempt[] = []): LeaseClaim[] {
|
|
291
329
|
const byToken = new Map<string, LeaseClaim>();
|
|
292
330
|
const order: string[] = [];
|
|
293
331
|
for (const e of events) {
|
|
294
332
|
let c = byToken.get(e.token);
|
|
295
333
|
if (!c) {
|
|
296
|
-
c = { token: e.token, holder: e.holder, acquiredAt: e.acquiredAt, expiresAt: e.expiresAt, renewals: 0, ended: "expired", release: null };
|
|
334
|
+
c = { token: e.token, holder: e.holder, acquiredAt: e.acquiredAt, expiresAt: e.expiresAt, renewals: 0, ended: "expired", release: null, attempt: false, kept: null };
|
|
297
335
|
byToken.set(e.token, c);
|
|
298
336
|
order.push(e.token);
|
|
299
337
|
}
|
|
@@ -307,10 +345,30 @@ export function claimsOf(events: LeaseHistoryRecord[], live: Pick<WorkLeaseState
|
|
|
307
345
|
else if (live && live.token === token) c.ended = live.state === "active" ? "held" : "expired";
|
|
308
346
|
else if (i < order.length - 1) c.ended = "lost";
|
|
309
347
|
else c.ended = "expired";
|
|
348
|
+
c.attempt = c.ended === "released" ? outcomeCountsAsAttempt(c.release?.outcome) : c.ended !== "held";
|
|
349
|
+
c.kept = kept.find((k) => k.token === token)?.ref ?? null;
|
|
310
350
|
return c;
|
|
311
351
|
});
|
|
312
352
|
}
|
|
313
353
|
|
|
354
|
+
/**
|
|
355
|
+
* The attempt limit of a work record (#3147): the integer in the field the
|
|
356
|
+
* kind's `work.attempts.field` names, else the kind's `work.attempts.max`;
|
|
357
|
+
* null when the kind declares no attempts.
|
|
358
|
+
*/
|
|
359
|
+
export function attemptLimit(kind: LoadedRecordKind["kind"], data: Record<string, unknown> | null): number | null {
|
|
360
|
+
const spec = kind.work?.attempts;
|
|
361
|
+
if (!spec) return null;
|
|
362
|
+
const own = data?.[spec.field];
|
|
363
|
+
return typeof own === "number" && Number.isInteger(own) && own >= 1 ? own : spec.max;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Count `claims`' attempts against `limit`. */
|
|
367
|
+
export function attemptsOf(claims: readonly LeaseClaim[], limit: number | null): WorkAttempts {
|
|
368
|
+
const failed = claims.filter((c) => c.attempt).length;
|
|
369
|
+
return { failed, limit, remaining: limit === null ? null : Math.max(0, limit - failed), exhausted: limit !== null && failed >= limit };
|
|
370
|
+
}
|
|
371
|
+
|
|
314
372
|
/**
|
|
315
373
|
* Read work item `id`'s lease history (#2785): every claim, renew and release
|
|
316
374
|
* on `_leases/<id>.jsonl` in the ledger of the member owning the work kind,
|
|
@@ -322,12 +380,13 @@ export async function workHistory(req: { id: string; cwd: string; kind?: string
|
|
|
322
380
|
try {
|
|
323
381
|
if (!WORK_ITEM_ID_PATTERN.test(req.id) || req.id.includes("..")) throw new WorkError("work-item-unknown", `${JSON.stringify(req.id)} is not a work item id`);
|
|
324
382
|
if (!gitRoot(req.cwd)) throw new WorkError("not-a-git-repository", "a work lease's history is on the chant/lifecycle branch, and this directory is not in a git repository");
|
|
325
|
-
const { loaded, root } = await findWorkItem(req.id, req.cwd, req.kind);
|
|
383
|
+
const { loaded, root, record } = await findWorkItem(req.id, req.cwd, req.kind);
|
|
326
384
|
const leaseCwd = dirname(loaded.file);
|
|
327
|
-
const { prefix } = await resolveMemberLedger(leaseCwd);
|
|
385
|
+
const { prefix, members } = await resolveMemberLedger(leaseCwd);
|
|
328
386
|
const { records: events, malformed } = await readLeaseHistory(req.id, { cwd: leaseCwd });
|
|
329
387
|
const live = (await listWorkLeases({ cwd: leaseCwd, memberPrefix: prefix })).find((l) => l.item === req.id) ?? null;
|
|
330
|
-
const
|
|
388
|
+
const kept = await listKeptAttempts(req.id, { cwd: leaseCwd, members });
|
|
389
|
+
const claims = claimsOf(events, live, kept);
|
|
331
390
|
const count = (end: ClaimEnd) => claims.filter((c) => c.ended === end).length;
|
|
332
391
|
const outcomes: Record<string, number> = {};
|
|
333
392
|
for (const c of claims) if (c.release?.outcome) outcomes[c.release.outcome] = (outcomes[c.release.outcome] ?? 0) + 1;
|
|
@@ -342,6 +401,8 @@ export async function workHistory(req: { id: string; cwd: string; kind?: string
|
|
|
342
401
|
events,
|
|
343
402
|
malformed,
|
|
344
403
|
summary: { claims: claims.length, released: count("released"), held: count("held"), expired: count("expired"), lost: count("lost"), outcomes },
|
|
404
|
+
attempts: attemptsOf(claims, attemptLimit(loaded.kind, record.data)),
|
|
405
|
+
kept,
|
|
345
406
|
};
|
|
346
407
|
} catch (err) {
|
|
347
408
|
if (err instanceof WorkError) return { ...head, error: { code: err.code as WorkHistoryErrorCode, message: err.message } };
|
|
@@ -357,10 +418,73 @@ export function formatWorkHistory(doc: Extract<WorkHistoryDocument, { claims: un
|
|
|
357
418
|
lines.push(` ${c.acquiredAt} ${c.holder} token ${c.token} ${c.renewals} renewal${c.renewals === 1 ? "" : "s"} ${how}`);
|
|
358
419
|
}
|
|
359
420
|
if (doc.claims.length === 0) lines.push(" never claimed");
|
|
421
|
+
const a = doc.attempts;
|
|
422
|
+
if (a.limit !== null) lines.push(` ${a.failed} of ${a.limit} attempt${a.limit === 1 ? "" : "s"} used${a.exhausted ? "; left to people" : ""}`);
|
|
423
|
+
else if (a.failed > 0) lines.push(` ${a.failed} attempt${a.failed === 1 ? "" : "s"} counted; the kind declares no limit`);
|
|
424
|
+
for (const k of doc.kept) lines.push(` kept: ${k.ref} (${k.commit.slice(0, 12)})`);
|
|
360
425
|
if (doc.malformed > 0) lines.push(` ${doc.malformed} malformed line${doc.malformed === 1 ? "" : "s"} left out`);
|
|
361
426
|
return lines.join("\n");
|
|
362
427
|
}
|
|
363
428
|
|
|
429
|
+
/**
|
|
430
|
+
* `chant workspace work evidence <id> --holder <name> --token <token> --from
|
|
431
|
+
* <file|->` (#3159): the `workEvidence` activity (#2772) as a command, so a
|
|
432
|
+
* writer that is not an Op, such as hud, attaches evidence through chant under
|
|
433
|
+
* the lease it holds. `--from` gives the entry as JSON: criterion, result,
|
|
434
|
+
* title, a url or a path, and optionally as_of. It always prints the
|
|
435
|
+
* work-evidence document; exit 0 when the entry was appended, 1 otherwise.
|
|
436
|
+
*/
|
|
437
|
+
async function runWorkEvidence(ctx: CommandContext): Promise<number> {
|
|
438
|
+
const { args } = ctx;
|
|
439
|
+
const { attachWorkEvidence, WORK_EVIDENCE_CONTRACT_VERSION, WORK_EVIDENCE_OUTPUT_SCHEMA_ID } = await import("./work-evidence");
|
|
440
|
+
const print = (doc: unknown, code: number): number => {
|
|
441
|
+
console.log(JSON.stringify(doc, null, 2));
|
|
442
|
+
return code;
|
|
443
|
+
};
|
|
444
|
+
const fail = (message: string): number =>
|
|
445
|
+
print({ $schema: WORK_EVIDENCE_OUTPUT_SCHEMA_ID, contract: WORK_EVIDENCE_CONTRACT_VERSION, error: { code: "write-usage-invalid", message: `${message}\n${USAGE}` } }, 1);
|
|
446
|
+
const id = args.extraPositional2;
|
|
447
|
+
if (!id) return fail("evidence needs a work item id");
|
|
448
|
+
if (!args.holder) return fail("evidence needs --holder <name>: the lease's holder");
|
|
449
|
+
if (!args.token) return fail("evidence needs --token <token>: the lease's fencing token, as its claim printed it");
|
|
450
|
+
if (args.migrateFrom === undefined) return fail("evidence needs --from <file|->: the entry as JSON, with criterion, result, title and a url or a path");
|
|
451
|
+
for (const [flag, v] of [["--ttl", args.ttl], ["--outcome", args.outcome], ["--note", args.note]] as const) {
|
|
452
|
+
if (v !== undefined) return fail(`evidence takes no ${flag}`);
|
|
453
|
+
}
|
|
454
|
+
let fields: Record<string, unknown>;
|
|
455
|
+
try {
|
|
456
|
+
const { readFileSync } = await import("node:fs");
|
|
457
|
+
const { resolve } = await import("node:path");
|
|
458
|
+
const text = readFileSync(args.migrateFrom === "-" ? 0 : resolve(process.cwd(), args.migrateFrom), "utf-8");
|
|
459
|
+
const parsed: unknown = JSON.parse(text);
|
|
460
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("the entry is not a JSON object");
|
|
461
|
+
fields = parsed as Record<string, unknown>;
|
|
462
|
+
} catch (err) {
|
|
463
|
+
return print(
|
|
464
|
+
{ $schema: WORK_EVIDENCE_OUTPUT_SCHEMA_ID, contract: WORK_EVIDENCE_CONTRACT_VERSION, error: { code: "write-input-invalid", message: `--from ${args.migrateFrom} could not be read: ${err instanceof Error ? err.message : String(err)}` } },
|
|
465
|
+
1,
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
const known = ["criterion", "result", "title", "url", "path", "as_of"];
|
|
469
|
+
const unknown = Object.keys(fields).filter((k) => !known.includes(k));
|
|
470
|
+
if (unknown.length > 0) return fail(`the entry takes ${known.join(", ")}, not ${unknown.join(", ")}`);
|
|
471
|
+
const str = (k: string): string | undefined => (typeof fields[k] === "string" ? (fields[k] as string) : undefined);
|
|
472
|
+
const doc = await attachWorkEvidence({
|
|
473
|
+
cwd: process.cwd(),
|
|
474
|
+
item: id,
|
|
475
|
+
kind: args.kind,
|
|
476
|
+
holder: args.holder,
|
|
477
|
+
token: args.token,
|
|
478
|
+
criterion: str("criterion") ?? "",
|
|
479
|
+
result: fields.result as never,
|
|
480
|
+
title: str("title") ?? "",
|
|
481
|
+
url: str("url"),
|
|
482
|
+
path: str("path"),
|
|
483
|
+
asOf: str("as_of"),
|
|
484
|
+
});
|
|
485
|
+
return print(doc, "error" in doc ? 1 : 0);
|
|
486
|
+
}
|
|
487
|
+
|
|
364
488
|
async function runWorkHistory(ctx: CommandContext): Promise<number> {
|
|
365
489
|
const { args } = ctx;
|
|
366
490
|
const id = args.extraPositional2;
|
|
@@ -385,13 +509,14 @@ export async function runWorkspaceWork(ctx: CommandContext): Promise<number> {
|
|
|
385
509
|
const { args } = ctx;
|
|
386
510
|
const verb = args.extraPositional;
|
|
387
511
|
if (verb === "history") return runWorkHistory(ctx);
|
|
512
|
+
if (verb === "evidence") return runWorkEvidence(ctx);
|
|
388
513
|
const fail = (message: string): number => {
|
|
389
514
|
if (args.json) console.log(JSON.stringify({ $schema: WORK_LEASE_OUTPUT_SCHEMA_ID, contract: WORK_LEASE_CONTRACT_VERSION, error: { code: "write-usage-invalid", message } }, null, 2));
|
|
390
515
|
else console.error(formatError({ message, hint: USAGE }));
|
|
391
516
|
return 1;
|
|
392
517
|
};
|
|
393
518
|
if (verb !== "claim" && verb !== "renew" && verb !== "release") {
|
|
394
|
-
return fail(verb ? `chant workspace work takes claim, renew, release or
|
|
519
|
+
return fail(verb ? `chant workspace work takes claim, renew, release, history or evidence, not ${verb}` : "chant workspace work needs claim, renew, release, history or evidence");
|
|
395
520
|
}
|
|
396
521
|
const id = args.extraPositional2;
|
|
397
522
|
if (!id) return fail(`${verb} needs a work item id`);
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
},
|
|
66
66
|
"outcome": {
|
|
67
67
|
"type": "string",
|
|
68
|
-
"description": "How the work ended, on a release
|
|
68
|
+
"description": "How the work ended, on a release: one of the closed list in work-lease.schema.json's $defs/outcome (#3147), done, not_done, abandoned, gated, waiting, dropped, redraft or ask. A line written before #3147 may hold other free text."
|
|
69
69
|
},
|
|
70
70
|
"note": {
|
|
71
71
|
"type": "string"
|
|
@@ -146,6 +146,17 @@
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
]
|
|
149
|
+
},
|
|
150
|
+
"attempt": {
|
|
151
|
+
"type": "boolean",
|
|
152
|
+
"description": "Added in contract 1 by #3147. Whether the claim counts toward the item's attempt limit: released not_done or abandoned, released with no outcome or with free text from before the outcomes were closed, or ended expired or lost. Never one that is held, and never one released done, gated, waiting, dropped, redraft or ask."
|
|
153
|
+
},
|
|
154
|
+
"kept": {
|
|
155
|
+
"type": [
|
|
156
|
+
"string",
|
|
157
|
+
"null"
|
|
158
|
+
],
|
|
159
|
+
"description": "Added in contract 1 by #3147. The ref keeping the claim's unfinished work, refs/chant/kept/[<member>/]<item>/<token>, written when a run that changes the checkout ended not_done; null when there is none."
|
|
149
160
|
}
|
|
150
161
|
}
|
|
151
162
|
},
|
|
@@ -300,6 +311,67 @@
|
|
|
300
311
|
"description": "How many released claims gave each outcome."
|
|
301
312
|
}
|
|
302
313
|
}
|
|
314
|
+
},
|
|
315
|
+
"attempts": {
|
|
316
|
+
"type": "object",
|
|
317
|
+
"description": "Added in contract 1 by #3147. The item's attempts against its limit. limit is the item's own field named by the kind's work.attempts.field, else the kind's work.attempts.max, else null. A runner leaves an exhausted item to people.",
|
|
318
|
+
"required": [
|
|
319
|
+
"failed",
|
|
320
|
+
"limit",
|
|
321
|
+
"remaining",
|
|
322
|
+
"exhausted"
|
|
323
|
+
],
|
|
324
|
+
"properties": {
|
|
325
|
+
"failed": {
|
|
326
|
+
"type": "integer",
|
|
327
|
+
"minimum": 0,
|
|
328
|
+
"description": "The claims whose attempt is true."
|
|
329
|
+
},
|
|
330
|
+
"limit": {
|
|
331
|
+
"type": [
|
|
332
|
+
"integer",
|
|
333
|
+
"null"
|
|
334
|
+
],
|
|
335
|
+
"minimum": 1
|
|
336
|
+
},
|
|
337
|
+
"remaining": {
|
|
338
|
+
"type": [
|
|
339
|
+
"integer",
|
|
340
|
+
"null"
|
|
341
|
+
],
|
|
342
|
+
"minimum": 0,
|
|
343
|
+
"description": "limit minus failed, never below 0; null without a limit."
|
|
344
|
+
},
|
|
345
|
+
"exhausted": {
|
|
346
|
+
"type": "boolean",
|
|
347
|
+
"description": "True once failed reaches the limit; false without one."
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"kept": {
|
|
352
|
+
"type": "array",
|
|
353
|
+
"description": "Added in contract 1 by #3147. Every kept attempt of the item, from the local refs under refs/chant/kept/[<member>/]<item>/, sorted by ref.",
|
|
354
|
+
"items": {
|
|
355
|
+
"type": "object",
|
|
356
|
+
"required": [
|
|
357
|
+
"token",
|
|
358
|
+
"ref",
|
|
359
|
+
"commit"
|
|
360
|
+
],
|
|
361
|
+
"properties": {
|
|
362
|
+
"token": {
|
|
363
|
+
"type": "string",
|
|
364
|
+
"description": "The lease token the attempt ran under."
|
|
365
|
+
},
|
|
366
|
+
"ref": {
|
|
367
|
+
"type": "string"
|
|
368
|
+
},
|
|
369
|
+
"commit": {
|
|
370
|
+
"type": "string",
|
|
371
|
+
"description": "The commit the ref points at."
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
303
375
|
}
|
|
304
376
|
}
|
|
305
377
|
},
|
|
@@ -9,14 +9,14 @@ import { join } from "node:path";
|
|
|
9
9
|
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
10
10
|
import { cleanScratch, contract, git, REPO, repo } from "./__fixtures__/contract-repo";
|
|
11
11
|
import { claimWorkLease, releaseWorkLease, renewWorkLease, type LeaseHistoryRecord } from "../lifecycle/work-lease";
|
|
12
|
-
import { claimsOf, formatWorkHistory, workHistory, type WorkHistoryDocument } from "./work-cli";
|
|
12
|
+
import { claimsOf, formatWorkHistory, workHistory, workLease, type WorkHistoryDocument } from "./work-cli";
|
|
13
13
|
import historySchema from "./work-history.schema.json";
|
|
14
14
|
|
|
15
15
|
const REF = join(REPO, "reference-workspace");
|
|
16
16
|
const { expectValid } = contract(historySchema);
|
|
17
17
|
|
|
18
|
-
function work(id: string): string {
|
|
19
|
-
const data = { schema: 1, id, title: `Work ${id}`, state: "open", implements: [], needs: [], constrains: ["path:app/server.mjs"], evidence: [], opened_on: "2026-09-25", source: { kind: "workspace", member: "app" }, supersedes: [] };
|
|
18
|
+
function work(id: string, extra: Record<string, unknown> = {}): string {
|
|
19
|
+
const data = { schema: 1, id, title: `Work ${id}`, state: "open", implements: [], needs: [], constrains: ["path:app/server.mjs"], evidence: [], opened_on: "2026-09-25", source: { kind: "workspace", member: "app" }, supersedes: [], ...extra };
|
|
20
20
|
return `---\n${JSON.stringify(data, null, 2)}\n---\n\n# ${id}\n`;
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -46,6 +46,7 @@ beforeAll(() => {
|
|
|
46
46
|
"design/work/work.schema.json": readFileSync(join(REF, "work", "work.schema.json"), "utf-8"),
|
|
47
47
|
"design/work/W-001-one.md": work("W-001"),
|
|
48
48
|
"design/work/W-002-two.md": work("W-002"),
|
|
49
|
+
"design/work/W-004-four.md": work("W-004", { max_attempts: 2 }),
|
|
49
50
|
});
|
|
50
51
|
git(root, "config", "user.name", "History Test");
|
|
51
52
|
git(root, "config", "user.email", "history@example.com");
|
|
@@ -88,6 +89,10 @@ describe("workspace work history (#2785)", () => {
|
|
|
88
89
|
]);
|
|
89
90
|
expect(doc.events.map((e) => e.event)).toEqual(["claim", "renew", "release", "claim", "claim"]);
|
|
90
91
|
expect(doc.summary).toEqual({ claims: 3, released: 1, held: 0, expired: 1, lost: 1, outcomes: { not_done: 1 } });
|
|
92
|
+
// Each of the three counts as an attempt (#3147): not_done, lost and expired, against the kind's default of 3.
|
|
93
|
+
expect(doc.claims.map((c) => c.attempt)).toEqual([true, true, true]);
|
|
94
|
+
expect(doc.attempts).toEqual({ failed: 3, limit: 3, remaining: 0, exhausted: true });
|
|
95
|
+
expect(formatWorkHistory(doc)).toContain("3 of 3 attempts used; left to people");
|
|
91
96
|
// The history file is where git would find it, under the member's prefix.
|
|
92
97
|
expect(git(root, "show", `chant/lifecycle:${doc.ledger.path}`).split("\n")).toHaveLength(5);
|
|
93
98
|
expect(formatWorkHistory(doc)).toContain("released not_done by steward/dispatch@1");
|
|
@@ -107,6 +112,37 @@ describe("workspace work history (#2785)", () => {
|
|
|
107
112
|
expect(doc.claims.map((c) => c.ended)).toEqual(["released", "held"]);
|
|
108
113
|
expect(doc.lease).toMatchObject({ holder: "worker-b", token: live.lease.token, state: "active" });
|
|
109
114
|
expect(doc.summary.outcomes).toEqual({ done: 1 });
|
|
115
|
+
expect(doc.attempts).toEqual({ failed: 0, limit: 3, remaining: 3, exhausted: false });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("outcomes that built nothing don't count as attempts, the item's max_attempts overrides the kind's, and kept attempts are listed (#3147)", async () => {
|
|
119
|
+
const now = () => new Date();
|
|
120
|
+
const tokens: string[] = [];
|
|
121
|
+
for (const outcome of ["dropped", "redraft", "ask", "waiting", "gated", "not_done"]) {
|
|
122
|
+
const c = await claimWorkLease("W-004", "worker-a", { cwd: kindDir, now });
|
|
123
|
+
if (!c.ok) throw new Error(c.message);
|
|
124
|
+
tokens.push(c.lease.token);
|
|
125
|
+
await releaseWorkLease("W-004", "worker-a", { cwd: kindDir, now, token: c.lease.token, outcome });
|
|
126
|
+
}
|
|
127
|
+
// The not_done attempt's work, kept the way a run that changes the checkout keeps it.
|
|
128
|
+
const kept = `refs/chant/kept/design/W-004/${tokens[5]}`;
|
|
129
|
+
git(root, "update-ref", kept, "HEAD");
|
|
130
|
+
|
|
131
|
+
const doc = await workHistory({ id: "W-004", cwd: root });
|
|
132
|
+
expectValid(doc);
|
|
133
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
134
|
+
expect(doc.claims.map((c) => c.attempt)).toEqual([false, false, false, false, false, true]);
|
|
135
|
+
expect(doc.attempts).toEqual({ failed: 1, limit: 2, remaining: 1, exhausted: false });
|
|
136
|
+
expect(doc.claims.map((c) => c.kept)).toEqual([null, null, null, null, null, kept]);
|
|
137
|
+
expect(doc.kept).toEqual([{ token: tokens[5], ref: kept, commit: git(root, "rev-parse", "HEAD").trim() }]);
|
|
138
|
+
expect(formatWorkHistory(doc)).toContain(`kept: ${kept}`);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("a release outside the closed list of outcomes is refused (#3147)", async () => {
|
|
142
|
+
await expect(releaseWorkLease("W-002", "worker-b", { cwd: kindDir, outcome: "skipped" })).rejects.toThrow(/not a work lease outcome/);
|
|
143
|
+
const doc = await workLease({ verb: "release", id: "W-002", holder: "worker-b", cwd: root, outcome: "skipped" });
|
|
144
|
+
expect(doc).toMatchObject({ error: { code: "write-usage-invalid" } });
|
|
145
|
+
expect((doc as { error: { message: string } }).error.message).toMatch(/done, not_done, abandoned, gated, waiting, dropped, redraft, ask/);
|
|
110
146
|
});
|
|
111
147
|
|
|
112
148
|
test("an item never claimed has no claims; an unknown item is an error", async () => {
|
|
@@ -142,12 +178,19 @@ describe("claimsOf", () => {
|
|
|
142
178
|
});
|
|
143
179
|
|
|
144
180
|
test("a release by someone closing out an expired lease keeps the holder and records who released it", () => {
|
|
145
|
-
const claims = claimsOf([e("claim", "a"), e("release", "a", { by: "janitor", outcome: "
|
|
181
|
+
const claims = claimsOf([e("claim", "a"), e("release", "a", { by: "janitor", outcome: "abandoned" })], null);
|
|
146
182
|
expect(claims).toEqual([
|
|
147
|
-
{ token: "a", holder: "h-a", acquiredAt: "2026-09-25T00:00:00.000Z", expiresAt: "2026-09-25T00:10:00.000Z", renewals: 0, ended: "released", release: { by: "janitor", at: "2026-09-25T00:00:00.000Z", outcome: "
|
|
183
|
+
{ token: "a", holder: "h-a", acquiredAt: "2026-09-25T00:00:00.000Z", expiresAt: "2026-09-25T00:10:00.000Z", renewals: 0, ended: "released", release: { by: "janitor", at: "2026-09-25T00:00:00.000Z", outcome: "abandoned", note: null }, attempt: true, kept: null },
|
|
148
184
|
]);
|
|
149
185
|
});
|
|
150
186
|
|
|
187
|
+
test("a release with free text from before the outcomes were closed counts as an attempt, and one with no outcome too", () => {
|
|
188
|
+
expect(claimsOf([e("claim", "a"), e("release", "a", { outcome: "skipped" })], null)[0].attempt).toBe(true);
|
|
189
|
+
expect(claimsOf([e("claim", "a"), e("release", "a")], null)[0].attempt).toBe(true);
|
|
190
|
+
expect(claimsOf([e("claim", "a"), e("release", "a", { outcome: "done" })], null)[0].attempt).toBe(false);
|
|
191
|
+
expect(claimsOf([e("claim", "a")], { token: "a", state: "active" })[0].attempt).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
|
|
151
194
|
test("the live ref decides between held and expired for the newest claim", () => {
|
|
152
195
|
expect(claimsOf([e("claim", "a")], { token: "a", state: "active" })[0].ended).toBe("held");
|
|
153
196
|
expect(claimsOf([e("claim", "a")], { token: "a", state: "expired" })[0].ended).toBe("expired");
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #3147 — the work kind's fields that replace studio's x-factory on work
|
|
3
|
+
* items: a contract link read with the contract's state, a builder tier from
|
|
4
|
+
* the tiers the kind declares, decision-point answers joined by the item's id
|
|
5
|
+
* (from the tree, and from the lifecycle ledger where a steward asked them),
|
|
6
|
+
* the box's intent as a source, an attempt limit, and result.lease.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
12
|
+
import { cleanScratch, contract, git, REPO, repo } from "./__fixtures__/contract-repo";
|
|
13
|
+
import { writeBlobToPath } from "../lifecycle/git";
|
|
14
|
+
import { answersLedgerDir } from "./answers-ledger";
|
|
15
|
+
import { parseFrontMatter } from "./records";
|
|
16
|
+
import { queryRecords } from "./records-cli";
|
|
17
|
+
import { newRecord } from "./records-write";
|
|
18
|
+
import recordsSchema from "./records.schema.json";
|
|
19
|
+
|
|
20
|
+
const REF = join(REPO, "reference-workspace");
|
|
21
|
+
const ref = (path: string) => readFileSync(join(REF, path), "utf-8");
|
|
22
|
+
const records = contract(recordsSchema);
|
|
23
|
+
|
|
24
|
+
const DECISION = (() => {
|
|
25
|
+
const fm = parseFrontMatter(ref("decisions/ref-001-how-the-app-is-deployed.md"));
|
|
26
|
+
if (!fm.ok) throw new Error(fm.message);
|
|
27
|
+
return fm.value;
|
|
28
|
+
})();
|
|
29
|
+
|
|
30
|
+
function decision(id: string, state = "decided"): string {
|
|
31
|
+
const proposed = state === "proposed";
|
|
32
|
+
const data = { ...DECISION, id, title: `Record ${id}`, state, supersedes: [], evidence: [], constrains: ["member:app"], ...(proposed ? { choice: null, decided_by: null, decided_on: null } : {}) };
|
|
33
|
+
return `---\n${JSON.stringify(data, null, 2)}\n---\n\n# ${id}\n`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function work(id: string, fields: Record<string, unknown> = {}): string {
|
|
37
|
+
const data = { schema: 1, id, title: `Work ${id}`, state: "open", implements: [], needs: [], constrains: ["member:app"], evidence: [], opened_on: "2026-10-03", source: { kind: "workspace", member: "app" }, supersedes: [], ...fields };
|
|
38
|
+
return `---\n${JSON.stringify(data, null, 2)}\n---\n\n# ${id}\n`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The reference slice-tier answer, about `item`, under the id `id`. */
|
|
42
|
+
function answer(id: string, item: string, extra: Record<string, unknown> = {}): string {
|
|
43
|
+
const fm = parseFrontMatter(ref("answers/slice-tier-01eb5382958d.md"));
|
|
44
|
+
if (!fm.ok) throw new Error(fm.message);
|
|
45
|
+
const data = { ...fm.value, id, constrains: [item], ...extra };
|
|
46
|
+
return `---\n${JSON.stringify(data, null, 2)}\n---\n\n# ${id}\n`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The reference work kind, with a contract kind: here the decision kind's shape, kept in contracts/.
|
|
50
|
+
const KIND = ref("work/work.kind.mjs").replace(
|
|
51
|
+
' answers: "../answers/answer.kind.mjs",\n',
|
|
52
|
+
' answers: "../answers/answer.kind.mjs",\n contract: { field: "contract", kind: "../contracts/decision.kind.mjs" },\n',
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
let root: string;
|
|
56
|
+
|
|
57
|
+
beforeAll(async () => {
|
|
58
|
+
expect(KIND).toContain("../contracts/decision.kind.mjs");
|
|
59
|
+
root = repo({
|
|
60
|
+
"chant.workspace.json": JSON.stringify({ name: "box", schema: 1, members: [{ name: "app", dir: "app", kind: "other", because: "the app" }] }),
|
|
61
|
+
"app/server.mjs": "export const port = 8080;\n",
|
|
62
|
+
"decisions/decision.kind.mjs": ref("decisions/decision.kind.mjs"),
|
|
63
|
+
"decisions/decision.schema.json": ref("decisions/decision.schema.json"),
|
|
64
|
+
"decisions/points.json": ref("decisions/points.json"),
|
|
65
|
+
"decisions/intent-001-what-to-build.md": decision("intent-001"),
|
|
66
|
+
"contracts/decision.kind.mjs": ref("decisions/decision.kind.mjs"),
|
|
67
|
+
"contracts/decision.schema.json": ref("decisions/decision.schema.json"),
|
|
68
|
+
"contracts/con-001-notes.md": decision("con-001"),
|
|
69
|
+
"contracts/con-002-draft.md": decision("con-002", "proposed"),
|
|
70
|
+
"answers/answer.kind.mjs": ref("answers/answer.kind.mjs"),
|
|
71
|
+
"answers/answer.schema.json": ref("answers/answer.schema.json"),
|
|
72
|
+
"answers/slice-tier-0123456789ab.md": answer("slice-tier-0123456789ab", "W-001"),
|
|
73
|
+
"work/work.kind.mjs": KIND,
|
|
74
|
+
"work/work.schema.json": ref("work/work.schema.json"),
|
|
75
|
+
"work/W-001-built.md": work("W-001", { contract: "con-001", tier: "small", max_attempts: 5, result: { lease: "6f0c11aa" } }),
|
|
76
|
+
"work/W-002-draft.md": work("W-002", { contract: "con-002", tier: "huge" }),
|
|
77
|
+
"work/W-003-missing.md": work("W-003", { contract: "con-404" }),
|
|
78
|
+
"work/W-004-plain.md": work("W-004"),
|
|
79
|
+
"work/W-005-intent.md": work("W-005", {
|
|
80
|
+
implements: ["intent-001"],
|
|
81
|
+
source: { intent: { decision: "intent-001", question: "What should the box build?", answer: "A notes app", by: "morgan", at: "2026-10-03T09:00:00Z" } },
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
git(root, "add", "-A");
|
|
85
|
+
git(root, "commit", "-q", "-m", "the box");
|
|
86
|
+
// A question a steward asked in its turn sits on the lifecycle ledger, never in the checkout (#2786).
|
|
87
|
+
await writeBlobToPath(answersLedgerDir("answer"), "understand-0123456789ab.md", answer("understand-0123456789ab", "W-001", { point: "understand", answer: "proceed", answered_by: ["morgan"] }), "ask understand", { cwd: join(root, "answers") });
|
|
88
|
+
});
|
|
89
|
+
afterAll(cleanScratch);
|
|
90
|
+
|
|
91
|
+
describe("the work kind's fields (#3147)", () => {
|
|
92
|
+
test("contract links with the contract's state, tier and contract warnings, and answers joined by id from the tree and the ledger", async () => {
|
|
93
|
+
const doc = await queryRecords({ kind: "work/work.kind.mjs", cwd: root });
|
|
94
|
+
records.expectValid(doc);
|
|
95
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
96
|
+
expect(doc.summary).toMatchObject({ total: 5, valid: 5, invalid: 0 });
|
|
97
|
+
const by = Object.fromEntries(doc.records.map((r) => [r.id, r]));
|
|
98
|
+
const codes = (id: string) => by[id].warnings.map((w: { code: string }) => w.code);
|
|
99
|
+
|
|
100
|
+
expect(by["W-001"].contract).toEqual({ id: "con-001", state: "decided" });
|
|
101
|
+
expect(codes("W-001")).toEqual([]);
|
|
102
|
+
expect(by["W-002"].contract).toEqual({ id: "con-002", state: "proposed" });
|
|
103
|
+
expect(codes("W-002")).toEqual(["work-contract-undecided", "work-tier-unknown"]);
|
|
104
|
+
expect(by["W-003"].contract).toEqual({ id: "con-404", state: null });
|
|
105
|
+
expect(codes("W-003")).toEqual(["work-contract-unknown"]);
|
|
106
|
+
expect(by["W-004"].contract).toBeNull();
|
|
107
|
+
|
|
108
|
+
// The item carries neither answer; the read joins both by its id.
|
|
109
|
+
expect(by["W-001"].answers).toEqual([
|
|
110
|
+
{ id: "slice-tier-0123456789ab", point: "slice-tier", state: "answered", answer: "small", answeredBy: [] },
|
|
111
|
+
{ id: "understand-0123456789ab", point: "understand", state: "answered", answer: "proceed", answeredBy: ["morgan"] },
|
|
112
|
+
]);
|
|
113
|
+
expect(by["W-002"].answers).toEqual([]);
|
|
114
|
+
expect(by["W-005"].data).toMatchObject({ source: { intent: { decision: "intent-001", answer: "A notes app", by: "morgan" } } });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("at a revision, answers come from that tree only: the ledger is not part of it", async () => {
|
|
118
|
+
const doc = await queryRecords({ kind: "work/work.kind.mjs", cwd: root, at: "HEAD" });
|
|
119
|
+
records.expectValid(doc);
|
|
120
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
121
|
+
expect(doc.records.find((r) => r.id === "W-001")!.answers!.map((a) => a.id)).toEqual(["slice-tier-0123456789ab"]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("the schema keeps only the lease under result, and an intent source needs its decision, answer and who decided", async () => {
|
|
125
|
+
const fields = (over: Record<string, unknown>) =>
|
|
126
|
+
JSON.stringify({ schema: 1, title: "More", state: "open", implements: [], needs: [], constrains: ["member:app"], evidence: [], opened_on: "2026-10-03", source: { kind: "workspace", member: "app" }, supersedes: [], ...over });
|
|
127
|
+
const kind = "work/work.kind.mjs";
|
|
128
|
+
const ok = await newRecord({ kind, fields: fields({ result: { lease: "6f0c" }, max_attempts: 2, tier: "large", contract: "con-001" }), dryRun: true, cwd: root });
|
|
129
|
+
expect("error" in ok ? ok.error : null).toBeNull();
|
|
130
|
+
for (const over of [
|
|
131
|
+
{ result: { lease: "6f0c", agent: "builder" } },
|
|
132
|
+
{ max_attempts: 0 },
|
|
133
|
+
{ source: { intent: { decision: "intent-001", answer: "A notes app" } } },
|
|
134
|
+
{ source: { intent: { decision: "intent-001", by: "morgan" } } },
|
|
135
|
+
]) {
|
|
136
|
+
const doc = await newRecord({ kind, fields: fields(over), dryRun: true, cwd: root });
|
|
137
|
+
expect("error" in doc && doc.error.code, JSON.stringify(over)).toBe("record-schema-invalid");
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("a kind that lists a tier twice, or names an attempt limit below one, is kind-invalid", async () => {
|
|
142
|
+
for (const [from, to] of [
|
|
143
|
+
['tiers: ["small", "medium", "large"]', 'tiers: ["small", "small"]'],
|
|
144
|
+
['max: 3', 'max: 0'],
|
|
145
|
+
]) {
|
|
146
|
+
const dir = repo({
|
|
147
|
+
"work/work.kind.mjs": ref("work/work.kind.mjs").replace(from, to),
|
|
148
|
+
"work/work.schema.json": ref("work/work.schema.json"),
|
|
149
|
+
});
|
|
150
|
+
const doc = await queryRecords({ kind: "work/work.kind.mjs", cwd: dir });
|
|
151
|
+
expect("error" in doc && doc.error.code, to).toBe("kind-invalid");
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://intentius.io/chant/schemas/workspace/work-lease/v1/work-lease.schema.json",
|
|
4
4
|
"title": "chant workspace work output",
|
|
5
|
-
"description": "What `chant workspace work claim|renew|release <id> --holder <name> --json` prints (#2732, ws-055): the work lease it wrote, the reason it was refused, or the reason the command could not run. Version 1 of the write contract for work leases. The lease is the ref refs/chant/lease/[_members/<member>/]work/<id>; each write also appends to _leases/<id>.jsonl on chant/lifecycle. Readers ignore fields they do not know; a field is only ever added within a version. The refusal and error codes are closed lists, each in the one closed list of `reason-codes.ts`.",
|
|
5
|
+
"description": "What `chant workspace work claim|renew|release <id> --holder <name> --json` prints (#2732, ws-055): the work lease it wrote, the reason it was refused, or the reason the command could not run. Version 1 of the write contract for work leases. The lease is the ref refs/chant/lease/[_members/<member>/]work/<id>; each write also appends to _leases/<id>.jsonl on chant/lifecycle. Readers ignore fields they do not know; a field is only ever added within a version. The refusal and error codes are closed lists, each in the one closed list of `reason-codes.ts`. A release's --outcome is one of the closed list `$defs/outcome` (#3147); any other value is refused with write-usage-invalid.",
|
|
6
6
|
"oneOf": [
|
|
7
7
|
{
|
|
8
8
|
"$ref": "#/$defs/result"
|
|
@@ -15,6 +15,19 @@
|
|
|
15
15
|
}
|
|
16
16
|
],
|
|
17
17
|
"$defs": {
|
|
18
|
+
"outcome": {
|
|
19
|
+
"description": "How a work lease ended, as a release records it on _leases/<id>.jsonl (#3147). Closed: a new outcome is a contract change. done: the work was finished. not_done: a build ran and did not finish, or the run failed; counts as an attempt. abandoned: an expired lease closed out by someone other than its holder; counts as an attempt. gated: the run stopped at a gate. waiting: the run stopped on a decision point's open question. dropped: the item was dropped, such as by a refuse at the understand point. redraft and ask: the understand point sent the item back to its author. Only not_done and abandoned count toward an item's attempt limit.",
|
|
20
|
+
"enum": [
|
|
21
|
+
"done",
|
|
22
|
+
"not_done",
|
|
23
|
+
"abandoned",
|
|
24
|
+
"gated",
|
|
25
|
+
"waiting",
|
|
26
|
+
"dropped",
|
|
27
|
+
"redraft",
|
|
28
|
+
"ask"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
18
31
|
"lease": {
|
|
19
32
|
"type": "object",
|
|
20
33
|
"required": [
|