@intentius/chant 0.91.0 → 0.94.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/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +6 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-plugins.d.ts +6 -6
- package/dist/cli/mcp/workspace-tools.d.ts +2 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +12 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/lexicon-module.d.ts +22 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +19 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.d.ts +2 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/symptoms.d.ts +51 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -1
- package/dist/lint/rules/index.d.ts +4 -2
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.d.ts +14 -3
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
- package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
- package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
- package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
- package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
- package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
- package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
- package/dist/op/activities/activity-contracts.d.ts +201 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/change-coverage.d.ts +44 -0
- package/dist/op/activities/change-coverage.d.ts.map +1 -0
- package/dist/op/activities/converge.d.ts +24 -1
- package/dist/op/activities/converge.d.ts.map +1 -1
- package/dist/op/activities/decide.d.ts +107 -0
- package/dist/op/activities/decide.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +10 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/shell.d.ts +32 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/source-release.d.ts +86 -0
- package/dist/op/activities/source-release.d.ts.map +1 -0
- package/dist/op/activities/source-rollback.d.ts +100 -0
- package/dist/op/activities/source-rollback.d.ts.map +1 -0
- package/dist/op/activities/work-evidence.d.ts +42 -0
- package/dist/op/activities/work-evidence.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +1 -0
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts +16 -0
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts +84 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/converge-op.d.ts +31 -4
- package/dist/op/composites/converge-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +116 -0
- package/dist/op/decide-backend.d.ts.map +1 -0
- package/dist/op/decide-config.d.ts +120 -0
- package/dist/op/decide-config.d.ts.map +1 -0
- package/dist/op/decide-read-inputs.d.ts +40 -0
- package/dist/op/decide-read-inputs.d.ts.map +1 -0
- package/dist/op/gate-wait.d.ts +43 -0
- package/dist/op/gate-wait.d.ts.map +1 -0
- package/dist/op/gate.d.ts +5 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +11 -4
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +29 -4
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts +2 -0
- package/dist/op/local-output.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +2 -0
- package/dist/op/op-ir.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts +97 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +37 -5
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts +63 -0
- package/dist/op/source-archive.d.ts.map +1 -0
- package/dist/op/steward-points.d.ts +136 -0
- package/dist/op/steward-points.d.ts.map +1 -0
- package/dist/op/steward-turn.d.ts +53 -0
- package/dist/op/steward-turn.d.ts.map +1 -0
- package/dist/op/steward.d.ts +22 -1
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/types.d.ts +7 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts +1 -1
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
- package/dist/workspace/answers-ledger.d.ts +76 -0
- package/dist/workspace/answers-ledger.d.ts.map +1 -0
- package/dist/workspace/box-intent.d.ts +85 -0
- package/dist/workspace/box-intent.d.ts.map +1 -0
- package/dist/workspace/changes-cli.d.ts +16 -0
- package/dist/workspace/changes-cli.d.ts.map +1 -0
- package/dist/workspace/changes.d.ts +200 -0
- package/dist/workspace/changes.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
- package/dist/workspace/chant-migrations.d.ts +104 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -0
- package/dist/workspace/checks/boxes.d.ts +47 -1
- package/dist/workspace/checks/boxes.d.ts.map +1 -1
- package/dist/workspace/checks/diagrams.d.ts +31 -0
- package/dist/workspace/checks/diagrams.d.ts.map +1 -0
- package/dist/workspace/checks/records.d.ts +7 -0
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +7 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +2 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +49 -4
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/decision-points.schema.json +3 -3
- package/dist/workspace/declaration.d.ts +58 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +119 -1
- package/dist/workspace/declared-kinds.d.ts +20 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -1
- package/dist/workspace/generated-files.d.ts +7 -0
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/intent-joins.d.ts +5 -5
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/lineage-check.d.ts +3 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +6 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/ls.d.ts +39 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +95 -0
- package/dist/workspace/points-cli.d.ts +2 -0
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +4 -6
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +16 -2
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +11 -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-write.d.ts +11 -5
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +34 -4
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/status-gates.d.ts +7 -2
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-stewards.d.ts +65 -1
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +24 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +113 -2
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +74 -0
- package/dist/workspace/work-evidence.d.ts.map +1 -0
- package/dist/workspace/work.d.ts +43 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/audit.test.ts +9 -1
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/commands/lint.ts +7 -1
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +16 -2
- package/src/cli/handlers/operator.ts +34 -2
- package/src/cli/handlers/run.test.ts +154 -2
- package/src/cli/handlers/run.ts +121 -2
- package/src/cli/main.test.ts +10 -0
- package/src/cli/main.ts +23 -3
- package/src/cli/mcp/workspace-plugins.ts +6 -6
- package/src/cli/mcp/workspace-tools.test.ts +35 -19
- package/src/cli/mcp/workspace-tools.ts +62 -4
- package/src/cli/plugins.ts +11 -3
- package/src/cli/registry.ts +12 -2
- package/src/cli/serve-mcp-workspace.test.ts +6 -6
- package/src/codegen/pinned-upgrade.ts +2 -1
- package/src/codegen/release-wiring.test.ts +49 -2
- package/src/components/capability-plugin-loader.ts +3 -3
- package/src/components/cli-support.ts +2 -2
- package/src/config.ts +11 -0
- package/src/lexicon-module.ts +56 -1
- package/src/lexicon-package.test.ts +96 -0
- package/src/lifecycle/converge-ledger.ts +20 -0
- package/src/lifecycle/run-ledger.ts +6 -0
- package/src/lifecycle/symptoms.ts +95 -0
- package/src/lint/rules/index.ts +6 -2
- package/src/lint/rules/op/index.ts +17 -4
- package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
- package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
- package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
- package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
- package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
- package/src/lint/rules/sys001-no-literal-key.ts +67 -0
- package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
- package/src/op/activities/activity-contracts.ts +190 -1
- package/src/op/activities/apply.ts +7 -6
- package/src/op/activities/change-coverage.ts +60 -0
- package/src/op/activities/converge.test.ts +55 -1
- package/src/op/activities/converge.ts +208 -29
- package/src/op/activities/decide.live.test.ts +78 -0
- package/src/op/activities/decide.test.ts +308 -0
- package/src/op/activities/decide.ts +267 -0
- package/src/op/activities/index.ts +21 -0
- package/src/op/activities/shell.test.ts +91 -1
- package/src/op/activities/shell.ts +91 -2
- package/src/op/activities/source-release.test.ts +118 -0
- package/src/op/activities/source-release.ts +168 -0
- package/src/op/activities/source-rollback.test.ts +131 -0
- package/src/op/activities/source-rollback.ts +239 -0
- package/src/op/activities/work-evidence.ts +62 -0
- package/src/op/activity-contract-registry.test.ts +2 -0
- package/src/op/activity-contract-registry.ts +2 -2
- package/src/op/activity-contract.test.ts +12 -0
- package/src/op/activity-contract.ts +6 -1
- package/src/op/activity-profiles.ts +29 -0
- package/src/op/activity-registry.test.ts +1 -1
- package/src/op/activity-registry.ts +2 -2
- package/src/op/builders.ts +119 -5
- package/src/op/command-gate.test.ts +192 -0
- package/src/op/composites/converge-op.test.ts +27 -1
- package/src/op/composites/converge-op.ts +69 -15
- package/src/op/decide-backend.test.ts +44 -0
- package/src/op/decide-backend.ts +212 -0
- package/src/op/decide-config.test.ts +52 -0
- package/src/op/decide-config.ts +80 -0
- package/src/op/decide-read-inputs.ts +117 -0
- package/src/op/gate-approval.ts +2 -2
- package/src/op/gate-wait.ts +57 -0
- package/src/op/gate.ts +8 -3
- package/src/op/generate-pipeline.ts +2 -2
- package/src/op/index.ts +18 -3
- package/src/op/local-executor.ts +151 -15
- package/src/op/local-output.test.ts +33 -0
- package/src/op/local-output.ts +20 -2
- package/src/op/op-ir.test.ts +10 -0
- package/src/op/op-ir.ts +4 -0
- package/src/op/op-verb-class.ts +1 -0
- package/src/op/operator.ts +290 -11
- package/src/op/runtime.ts +31 -8
- package/src/op/runtimes/local.ts +3 -1
- package/src/op/source-archive.ts +161 -0
- package/src/op/steward-points.test.ts +364 -0
- package/src/op/steward-points.ts +194 -0
- package/src/op/steward-turn.ts +79 -0
- package/src/op/steward.test.ts +87 -3
- package/src/op/steward.ts +25 -1
- package/src/op/types.ts +7 -0
- package/src/op/work-lease-run.test.ts +20 -1
- package/src/op/work-lease-run.ts +2 -2
- package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
- package/src/workspace/answers-ledger.ts +118 -0
- package/src/workspace/box-intent.test.ts +205 -0
- package/src/workspace/box-intent.ts +159 -0
- package/src/workspace/changes-cli.ts +56 -0
- package/src/workspace/changes.schema.json +472 -0
- package/src/workspace/changes.test.ts +315 -0
- package/src/workspace/changes.ts +512 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
- package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
- package/src/workspace/chant-migrations.ts +169 -0
- package/src/workspace/check-contract.test.ts +4 -2
- package/src/workspace/check.schema.json +19 -5
- package/src/workspace/checks/boxes.test.ts +82 -2
- package/src/workspace/checks/boxes.ts +202 -1
- package/src/workspace/checks/diagrams.test.ts +134 -0
- package/src/workspace/checks/diagrams.ts +108 -0
- package/src/workspace/checks/records.ts +47 -0
- package/src/workspace/checks.ts +20 -2
- package/src/workspace/compose-graph.ts +2 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
- package/src/workspace/conformance/index.ts +5 -0
- package/src/workspace/decide.test.ts +1 -1
- package/src/workspace/decide.ts +156 -23
- package/src/workspace/decision-points.schema.json +3 -3
- package/src/workspace/declaration.schema.json +119 -1
- package/src/workspace/declaration.test.ts +58 -0
- package/src/workspace/declaration.ts +112 -0
- package/src/workspace/declared-kinds.test.ts +31 -5
- package/src/workspace/declared-kinds.ts +46 -1
- package/src/workspace/generated-files.ts +14 -0
- package/src/workspace/graph-cli.ts +1 -1
- package/src/workspace/graph.schema.json +9 -1
- package/src/workspace/intent-joins.test.ts +5 -5
- package/src/workspace/intent-joins.ts +5 -5
- package/src/workspace/intent.schema.json +8 -2
- package/src/workspace/intent.ts +3 -10
- package/src/workspace/lineage-check.ts +16 -1
- package/src/workspace/lineage-upgrade-cli.ts +1 -1
- package/src/workspace/lineage-upgrade.test.ts +70 -0
- package/src/workspace/lineage-upgrade.ts +81 -10
- package/src/workspace/ls-contract.test.ts +57 -0
- package/src/workspace/ls.schema.json +46 -1
- package/src/workspace/ls.ts +62 -3
- package/src/workspace/points-cli.ts +17 -3
- package/src/workspace/points-write.schema.json +11 -1
- package/src/workspace/points.schema.json +51 -0
- package/src/workspace/points.test.ts +16 -15
- package/src/workspace/points.ts +5 -17
- package/src/workspace/read-contract.test.ts +9 -1
- package/src/workspace/reason-codes.test.ts +18 -8
- package/src/workspace/reason-codes.ts +21 -2
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-cli.ts +27 -4
- package/src/workspace/records-close.schema.json +2 -0
- package/src/workspace/records-formats.test.ts +15 -15
- package/src/workspace/records-new.schema.json +2 -0
- package/src/workspace/records-review.schema.json +2 -0
- package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
- package/src/workspace/records-sessions-write.test.ts +2 -34
- package/src/workspace/records-since.test.ts +8 -7
- package/src/workspace/records-write.e2e.test.ts +108 -0
- package/src/workspace/records-write.test.ts +32 -41
- package/src/workspace/records-write.ts +33 -15
- package/src/workspace/records.schema.json +36 -2
- package/src/workspace/records.test.ts +49 -4
- package/src/workspace/records.ts +109 -3
- package/src/workspace/status-contract.test.ts +14 -8
- package/src/workspace/status-gates.ts +9 -4
- package/src/workspace/status-stewards.ts +155 -5
- package/src/workspace/status.schema.json +129 -8
- package/src/workspace/status.ts +46 -1
- package/src/workspace/work-acceptance.test.ts +274 -0
- package/src/workspace/{work-cli.test.ts → work-cli.e2e.test.ts} +1 -1
- package/src/workspace/work-cli.ts +193 -10
- package/src/workspace/work-evidence.schema.json +212 -0
- package/src/workspace/work-evidence.ts +169 -0
- package/src/workspace/work-history.schema.json +349 -0
- package/src/workspace/work-history.test.ts +156 -0
- package/src/workspace/{work-readiness-chud.test.ts → work-readiness.test.ts} +30 -32
- package/src/workspace/work.test.ts +20 -2
- package/src/workspace/work.ts +100 -1
- /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
- /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
- /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://intentius.io/chant/schemas/workspace/work-history/v1/work-history.schema.json",
|
|
4
|
+
"title": "chant workspace work history output",
|
|
5
|
+
"description": "What `chant workspace work history <id> --json` prints (#2785): one work item's lease history, read from _leases/<id>.jsonl on chant/lifecycle in the ledger of the member that owns the work kind, folded into claims, one per fencing token, each with how it ended. A runner counts failed attempts from it. It reads the local branch and lease refs and never fetches. Version 1 of the read contract. Readers ignore fields they do not know; a field is only ever added within a version. The error codes are a closed list, each in the one closed list of `reason-codes.ts`.",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{
|
|
8
|
+
"$ref": "#/$defs/result"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"$ref": "#/$defs/failure"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"$defs": {
|
|
15
|
+
"event": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"description": "One line of the history, as written by a claim, renew or release.",
|
|
18
|
+
"required": [
|
|
19
|
+
"version",
|
|
20
|
+
"event",
|
|
21
|
+
"item",
|
|
22
|
+
"holder",
|
|
23
|
+
"by",
|
|
24
|
+
"token",
|
|
25
|
+
"acquiredAt",
|
|
26
|
+
"expiresAt",
|
|
27
|
+
"timestamp"
|
|
28
|
+
],
|
|
29
|
+
"properties": {
|
|
30
|
+
"version": {
|
|
31
|
+
"const": 1
|
|
32
|
+
},
|
|
33
|
+
"event": {
|
|
34
|
+
"enum": [
|
|
35
|
+
"claim",
|
|
36
|
+
"renew",
|
|
37
|
+
"release"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"item": {
|
|
41
|
+
"type": "string"
|
|
42
|
+
},
|
|
43
|
+
"holder": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "The lease's holder."
|
|
46
|
+
},
|
|
47
|
+
"by": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": "Who wrote the line: the holder, or, for the release of an expired lease, whoever closed it out."
|
|
50
|
+
},
|
|
51
|
+
"token": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "The fencing token: new on every claim, kept by every renew."
|
|
54
|
+
},
|
|
55
|
+
"acquiredAt": {
|
|
56
|
+
"type": "string"
|
|
57
|
+
},
|
|
58
|
+
"expiresAt": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "The lease's expiry after this event; a release keeps the expiry it had."
|
|
61
|
+
},
|
|
62
|
+
"timestamp": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "When the event happened, ISO 8601."
|
|
65
|
+
},
|
|
66
|
+
"outcome": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "How the work ended, on a release. Free text such as done, not_done, gated or waiting."
|
|
69
|
+
},
|
|
70
|
+
"note": {
|
|
71
|
+
"type": "string"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"claim": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"description": "One claim of the item: one fencing token, from its claim to how it ended.",
|
|
78
|
+
"required": [
|
|
79
|
+
"token",
|
|
80
|
+
"holder",
|
|
81
|
+
"acquiredAt",
|
|
82
|
+
"expiresAt",
|
|
83
|
+
"renewals",
|
|
84
|
+
"ended",
|
|
85
|
+
"release"
|
|
86
|
+
],
|
|
87
|
+
"properties": {
|
|
88
|
+
"token": {
|
|
89
|
+
"type": "string"
|
|
90
|
+
},
|
|
91
|
+
"holder": {
|
|
92
|
+
"type": "string"
|
|
93
|
+
},
|
|
94
|
+
"acquiredAt": {
|
|
95
|
+
"type": "string"
|
|
96
|
+
},
|
|
97
|
+
"expiresAt": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "The expiry its last claim or renew set."
|
|
100
|
+
},
|
|
101
|
+
"renewals": {
|
|
102
|
+
"type": "integer",
|
|
103
|
+
"minimum": 0
|
|
104
|
+
},
|
|
105
|
+
"ended": {
|
|
106
|
+
"enum": [
|
|
107
|
+
"released",
|
|
108
|
+
"held",
|
|
109
|
+
"expired",
|
|
110
|
+
"lost"
|
|
111
|
+
],
|
|
112
|
+
"description": "released: given back, with release.outcome when one was given. held: it is the live lease now. expired: it ran out, and nobody released it or claimed the item since. lost: it ran out unreleased, and a later claim took the item."
|
|
113
|
+
},
|
|
114
|
+
"release": {
|
|
115
|
+
"oneOf": [
|
|
116
|
+
{
|
|
117
|
+
"type": "null"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"type": "object",
|
|
121
|
+
"required": [
|
|
122
|
+
"by",
|
|
123
|
+
"at",
|
|
124
|
+
"outcome",
|
|
125
|
+
"note"
|
|
126
|
+
],
|
|
127
|
+
"properties": {
|
|
128
|
+
"by": {
|
|
129
|
+
"type": "string"
|
|
130
|
+
},
|
|
131
|
+
"at": {
|
|
132
|
+
"type": "string"
|
|
133
|
+
},
|
|
134
|
+
"outcome": {
|
|
135
|
+
"type": [
|
|
136
|
+
"string",
|
|
137
|
+
"null"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"note": {
|
|
141
|
+
"type": [
|
|
142
|
+
"string",
|
|
143
|
+
"null"
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"result": {
|
|
153
|
+
"type": "object",
|
|
154
|
+
"description": "The history was read. Exit code 0. An item never claimed has no claims.",
|
|
155
|
+
"required": [
|
|
156
|
+
"$schema",
|
|
157
|
+
"contract",
|
|
158
|
+
"chant",
|
|
159
|
+
"item",
|
|
160
|
+
"kind",
|
|
161
|
+
"ref",
|
|
162
|
+
"ledger",
|
|
163
|
+
"lease",
|
|
164
|
+
"claims",
|
|
165
|
+
"events",
|
|
166
|
+
"malformed",
|
|
167
|
+
"summary"
|
|
168
|
+
],
|
|
169
|
+
"properties": {
|
|
170
|
+
"$schema": {
|
|
171
|
+
"const": "https://intentius.io/chant/schemas/workspace/work-history/v1/work-history.schema.json"
|
|
172
|
+
},
|
|
173
|
+
"contract": {
|
|
174
|
+
"const": 1
|
|
175
|
+
},
|
|
176
|
+
"chant": {
|
|
177
|
+
"type": "string"
|
|
178
|
+
},
|
|
179
|
+
"item": {
|
|
180
|
+
"type": "string"
|
|
181
|
+
},
|
|
182
|
+
"kind": {
|
|
183
|
+
"type": "string",
|
|
184
|
+
"description": "The work kind file, from the repository root."
|
|
185
|
+
},
|
|
186
|
+
"ref": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"description": "The lease ref: refs/chant/lease/[_members/<member>/]work/<id>."
|
|
189
|
+
},
|
|
190
|
+
"ledger": {
|
|
191
|
+
"type": "object",
|
|
192
|
+
"required": [
|
|
193
|
+
"branch",
|
|
194
|
+
"path"
|
|
195
|
+
],
|
|
196
|
+
"properties": {
|
|
197
|
+
"branch": {
|
|
198
|
+
"const": "chant/lifecycle"
|
|
199
|
+
},
|
|
200
|
+
"path": {
|
|
201
|
+
"type": "string",
|
|
202
|
+
"description": "The history file on the branch: [_members/<member>/]_leases/<id>.jsonl."
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"lease": {
|
|
207
|
+
"description": "The lease ref's record now, live or expired, or null when there is none. Read from the local ref and the remote's as last fetched.",
|
|
208
|
+
"oneOf": [
|
|
209
|
+
{
|
|
210
|
+
"type": "null"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"type": "object",
|
|
214
|
+
"required": [
|
|
215
|
+
"holder",
|
|
216
|
+
"token",
|
|
217
|
+
"acquiredAt",
|
|
218
|
+
"expiresAt",
|
|
219
|
+
"state"
|
|
220
|
+
],
|
|
221
|
+
"properties": {
|
|
222
|
+
"holder": {
|
|
223
|
+
"type": "string"
|
|
224
|
+
},
|
|
225
|
+
"token": {
|
|
226
|
+
"type": "string"
|
|
227
|
+
},
|
|
228
|
+
"acquiredAt": {
|
|
229
|
+
"type": "string"
|
|
230
|
+
},
|
|
231
|
+
"expiresAt": {
|
|
232
|
+
"type": "string"
|
|
233
|
+
},
|
|
234
|
+
"state": {
|
|
235
|
+
"enum": [
|
|
236
|
+
"active",
|
|
237
|
+
"expired"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
"claims": {
|
|
245
|
+
"type": "array",
|
|
246
|
+
"items": {
|
|
247
|
+
"$ref": "#/$defs/claim"
|
|
248
|
+
},
|
|
249
|
+
"description": "Each claim, oldest first."
|
|
250
|
+
},
|
|
251
|
+
"events": {
|
|
252
|
+
"type": "array",
|
|
253
|
+
"items": {
|
|
254
|
+
"$ref": "#/$defs/event"
|
|
255
|
+
},
|
|
256
|
+
"description": "Every line of the history, oldest first."
|
|
257
|
+
},
|
|
258
|
+
"malformed": {
|
|
259
|
+
"type": "integer",
|
|
260
|
+
"minimum": 0,
|
|
261
|
+
"description": "Lines that are not lease events, left out."
|
|
262
|
+
},
|
|
263
|
+
"summary": {
|
|
264
|
+
"type": "object",
|
|
265
|
+
"required": [
|
|
266
|
+
"claims",
|
|
267
|
+
"released",
|
|
268
|
+
"held",
|
|
269
|
+
"expired",
|
|
270
|
+
"lost",
|
|
271
|
+
"outcomes"
|
|
272
|
+
],
|
|
273
|
+
"properties": {
|
|
274
|
+
"claims": {
|
|
275
|
+
"type": "integer",
|
|
276
|
+
"minimum": 0
|
|
277
|
+
},
|
|
278
|
+
"released": {
|
|
279
|
+
"type": "integer",
|
|
280
|
+
"minimum": 0
|
|
281
|
+
},
|
|
282
|
+
"held": {
|
|
283
|
+
"type": "integer",
|
|
284
|
+
"minimum": 0
|
|
285
|
+
},
|
|
286
|
+
"expired": {
|
|
287
|
+
"type": "integer",
|
|
288
|
+
"minimum": 0
|
|
289
|
+
},
|
|
290
|
+
"lost": {
|
|
291
|
+
"type": "integer",
|
|
292
|
+
"minimum": 0
|
|
293
|
+
},
|
|
294
|
+
"outcomes": {
|
|
295
|
+
"type": "object",
|
|
296
|
+
"additionalProperties": {
|
|
297
|
+
"type": "integer",
|
|
298
|
+
"minimum": 1
|
|
299
|
+
},
|
|
300
|
+
"description": "How many released claims gave each outcome."
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"failure": {
|
|
307
|
+
"type": "object",
|
|
308
|
+
"description": "The history could not be read. Exit code 1.",
|
|
309
|
+
"required": [
|
|
310
|
+
"$schema",
|
|
311
|
+
"contract",
|
|
312
|
+
"chant",
|
|
313
|
+
"error"
|
|
314
|
+
],
|
|
315
|
+
"properties": {
|
|
316
|
+
"$schema": {
|
|
317
|
+
"const": "https://intentius.io/chant/schemas/workspace/work-history/v1/work-history.schema.json"
|
|
318
|
+
},
|
|
319
|
+
"contract": {
|
|
320
|
+
"const": 1
|
|
321
|
+
},
|
|
322
|
+
"chant": {
|
|
323
|
+
"type": "string"
|
|
324
|
+
},
|
|
325
|
+
"error": {
|
|
326
|
+
"type": "object",
|
|
327
|
+
"required": [
|
|
328
|
+
"code",
|
|
329
|
+
"message"
|
|
330
|
+
],
|
|
331
|
+
"properties": {
|
|
332
|
+
"code": {
|
|
333
|
+
"enum": [
|
|
334
|
+
"kind-unreadable",
|
|
335
|
+
"work-kind-missing",
|
|
336
|
+
"work-kind-ambiguous",
|
|
337
|
+
"work-item-unknown",
|
|
338
|
+
"not-a-git-repository"
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
"message": {
|
|
342
|
+
"type": "string"
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #2785 — `chant workspace work history <id>`: a work item's lease history,
|
|
3
|
+
* each claim with how it ended, from the ledger of the member that owns the
|
|
4
|
+
* work kind, so a runner counts failed builds without reading git itself.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
10
|
+
import { cleanScratch, contract, git, REPO, repo } from "./__fixtures__/contract-repo";
|
|
11
|
+
import { claimWorkLease, releaseWorkLease, renewWorkLease, type LeaseHistoryRecord } from "../lifecycle/work-lease";
|
|
12
|
+
import { claimsOf, formatWorkHistory, workHistory, type WorkHistoryDocument } from "./work-cli";
|
|
13
|
+
import historySchema from "./work-history.schema.json";
|
|
14
|
+
|
|
15
|
+
const REF = join(REPO, "reference-workspace");
|
|
16
|
+
const { expectValid } = contract(historySchema);
|
|
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: [] };
|
|
20
|
+
return `---\n${JSON.stringify(data, null, 2)}\n---\n\n# ${id}\n`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let root: string;
|
|
24
|
+
let kindDir: string;
|
|
25
|
+
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
// The work kind sits in the design member, so its leases are in that member's ledger.
|
|
28
|
+
root = repo({
|
|
29
|
+
"chant.workspace.json": JSON.stringify(
|
|
30
|
+
{
|
|
31
|
+
name: "studio",
|
|
32
|
+
schema: 1,
|
|
33
|
+
members: [
|
|
34
|
+
{ name: "app", dir: "app", kind: "other", because: "a plain Node server" },
|
|
35
|
+
{ name: "design", dir: "design", kind: "other", because: "records only" },
|
|
36
|
+
],
|
|
37
|
+
records: [{ kind: "design/decisions/decision.kind.mjs" }, { kind: "design/work/work.kind.mjs" }],
|
|
38
|
+
},
|
|
39
|
+
null,
|
|
40
|
+
2,
|
|
41
|
+
),
|
|
42
|
+
"app/server.mjs": "export const port = 8080;\n",
|
|
43
|
+
"design/decisions/decision.kind.mjs": readFileSync(join(REF, "decisions", "decision.kind.mjs"), "utf-8"),
|
|
44
|
+
"design/decisions/decision.schema.json": readFileSync(join(REF, "decisions", "decision.schema.json"), "utf-8"),
|
|
45
|
+
"design/work/work.kind.mjs": readFileSync(join(REF, "work", "work.kind.mjs"), "utf-8"),
|
|
46
|
+
"design/work/work.schema.json": readFileSync(join(REF, "work", "work.schema.json"), "utf-8"),
|
|
47
|
+
"design/work/W-001-one.md": work("W-001"),
|
|
48
|
+
"design/work/W-002-two.md": work("W-002"),
|
|
49
|
+
});
|
|
50
|
+
git(root, "config", "user.name", "History Test");
|
|
51
|
+
git(root, "config", "user.email", "history@example.com");
|
|
52
|
+
git(root, "add", "-A");
|
|
53
|
+
git(root, "commit", "-q", "-m", "the queue");
|
|
54
|
+
kindDir = join(root, "design", "work");
|
|
55
|
+
});
|
|
56
|
+
afterAll(cleanScratch);
|
|
57
|
+
|
|
58
|
+
const at = (min: number) => () => new Date(Date.UTC(2020, 0, 1, 12, min));
|
|
59
|
+
|
|
60
|
+
describe("workspace work history (#2785)", () => {
|
|
61
|
+
test("each claim with its token and holder, and how it ended: released with an outcome, lost, expired or held", async () => {
|
|
62
|
+
const opts = (min: number) => ({ cwd: kindDir, now: at(min), ttlMs: 10 * 60_000 });
|
|
63
|
+
// A build that ended not_done.
|
|
64
|
+
const first = await claimWorkLease("W-001", "steward/dispatch@1", opts(0));
|
|
65
|
+
if (!first.ok) throw new Error(first.message);
|
|
66
|
+
await renewWorkLease("W-001", "steward/dispatch@1", { ...opts(3), token: first.lease.token });
|
|
67
|
+
await releaseWorkLease("W-001", "steward/dispatch@1", { cwd: kindDir, now: at(5), token: first.lease.token, outcome: "not_done" });
|
|
68
|
+
// A build whose lease ran out unreleased, and another worker then claimed the item.
|
|
69
|
+
const second = await claimWorkLease("W-001", "steward/dispatch@2", opts(10));
|
|
70
|
+
if (!second.ok) throw new Error(second.message);
|
|
71
|
+
const third = await claimWorkLease("W-001", "steward/dispatch@3", opts(30));
|
|
72
|
+
if (!third.ok) throw new Error(third.message);
|
|
73
|
+
|
|
74
|
+
const doc = await workHistory({ id: "W-001", cwd: root });
|
|
75
|
+
expectValid(doc);
|
|
76
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
77
|
+
expect(doc).toMatchObject({
|
|
78
|
+
item: "W-001",
|
|
79
|
+
kind: "design/work/work.kind.mjs",
|
|
80
|
+
ref: "refs/chant/lease/_members/design/work/W-001",
|
|
81
|
+
ledger: { branch: "chant/lifecycle", path: "_members/design/_leases/W-001.jsonl" },
|
|
82
|
+
malformed: 0,
|
|
83
|
+
});
|
|
84
|
+
expect(doc.claims.map((c) => [c.holder, c.token, c.ended, c.renewals, c.release?.outcome ?? null])).toEqual([
|
|
85
|
+
["steward/dispatch@1", first.lease.token, "released", 1, "not_done"],
|
|
86
|
+
["steward/dispatch@2", second.lease.token, "lost", 0, null],
|
|
87
|
+
["steward/dispatch@3", third.lease.token, "expired", 0, null],
|
|
88
|
+
]);
|
|
89
|
+
expect(doc.events.map((e) => e.event)).toEqual(["claim", "renew", "release", "claim", "claim"]);
|
|
90
|
+
expect(doc.summary).toEqual({ claims: 3, released: 1, held: 0, expired: 1, lost: 1, outcomes: { not_done: 1 } });
|
|
91
|
+
// The history file is where git would find it, under the member's prefix.
|
|
92
|
+
expect(git(root, "show", `chant/lifecycle:${doc.ledger.path}`).split("\n")).toHaveLength(5);
|
|
93
|
+
expect(formatWorkHistory(doc)).toContain("released not_done by steward/dispatch@1");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("a live claim is held, and a done release is counted by outcome", async () => {
|
|
97
|
+
const now = () => new Date();
|
|
98
|
+
const done = await claimWorkLease("W-002", "worker-a", { cwd: kindDir, now });
|
|
99
|
+
if (!done.ok) throw new Error(done.message);
|
|
100
|
+
await releaseWorkLease("W-002", "worker-a", { cwd: kindDir, now, token: done.lease.token, outcome: "done" });
|
|
101
|
+
const live = await claimWorkLease("W-002", "worker-b", { cwd: kindDir, now });
|
|
102
|
+
if (!live.ok) throw new Error(live.message);
|
|
103
|
+
|
|
104
|
+
const doc = await workHistory({ id: "W-002", cwd: kindDir, kind: join(kindDir, "work.kind.mjs") });
|
|
105
|
+
expectValid(doc);
|
|
106
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
107
|
+
expect(doc.claims.map((c) => c.ended)).toEqual(["released", "held"]);
|
|
108
|
+
expect(doc.lease).toMatchObject({ holder: "worker-b", token: live.lease.token, state: "active" });
|
|
109
|
+
expect(doc.summary.outcomes).toEqual({ done: 1 });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("an item never claimed has no claims; an unknown item is an error", async () => {
|
|
113
|
+
const W3 = join(kindDir, "W-003-three.md");
|
|
114
|
+
writeFileSync(W3, work("W-003"));
|
|
115
|
+
try {
|
|
116
|
+
const none = await workHistory({ id: "W-003", cwd: root });
|
|
117
|
+
expectValid(none);
|
|
118
|
+
expect(none).toMatchObject({ claims: [], events: [], lease: null, summary: { claims: 0 } });
|
|
119
|
+
} finally {
|
|
120
|
+
rmSync(W3);
|
|
121
|
+
}
|
|
122
|
+
const unknown = await workHistory({ id: "W-404", cwd: root });
|
|
123
|
+
expectValid(unknown);
|
|
124
|
+
expect(unknown).toMatchObject({ error: { code: "work-item-unknown" } });
|
|
125
|
+
const bad = await workHistory({ id: "../x", cwd: root });
|
|
126
|
+
expect((bad as Extract<WorkHistoryDocument, { error: unknown }>).error.code).toBe("work-item-unknown");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("claimsOf", () => {
|
|
131
|
+
const e = (event: LeaseHistoryRecord["event"], token: string, extra: Partial<LeaseHistoryRecord> = {}): LeaseHistoryRecord => ({
|
|
132
|
+
version: 1,
|
|
133
|
+
event,
|
|
134
|
+
item: "W",
|
|
135
|
+
holder: `h-${token}`,
|
|
136
|
+
by: `h-${token}`,
|
|
137
|
+
token,
|
|
138
|
+
acquiredAt: "2026-09-25T00:00:00.000Z",
|
|
139
|
+
expiresAt: "2026-09-25T00:10:00.000Z",
|
|
140
|
+
timestamp: "2026-09-25T00:00:00.000Z",
|
|
141
|
+
...extra,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
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: "expired" })], null);
|
|
146
|
+
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: "expired", note: null } },
|
|
148
|
+
]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("the live ref decides between held and expired for the newest claim", () => {
|
|
152
|
+
expect(claimsOf([e("claim", "a")], { token: "a", state: "active" })[0].ended).toBe("held");
|
|
153
|
+
expect(claimsOf([e("claim", "a")], { token: "a", state: "expired" })[0].ended).toBe("expired");
|
|
154
|
+
expect(claimsOf([e("claim", "a"), e("claim", "b")], { token: "b", state: "active" }).map((c) => c.ended)).toEqual(["lost", "held"]);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The work kind's `ready`/`blockedBy` against
|
|
3
|
-
* #2734)
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* unit is open against it; a contract that is itself met leaves the queue.
|
|
2
|
+
* The work kind's `ready`/`blockedBy` against a reference ready-queue rule
|
|
3
|
+
* (ws-055, #2734), ported from chud's `readyQueue`
|
|
4
|
+
* (packages/runtime/src/ready.mjs at jhgaylor/chud 43afcf1): a contract
|
|
5
|
+
* enters the ready set once it is approved and in force, every `depends_on`
|
|
6
|
+
* id is `met` (drivers.mjs's `memberState`), and no unit is open against it;
|
|
7
|
+
* a contract that is itself met leaves the queue.
|
|
9
8
|
*
|
|
10
9
|
* Of those rules, the work kind's `ready` and `blockedBy` (work.ts) can
|
|
11
10
|
* express two directly, with no new field:
|
|
@@ -17,22 +16,21 @@
|
|
|
17
16
|
* retired contract does.
|
|
18
17
|
* - a record that is itself already `done` (or `dropped`, or merely
|
|
19
18
|
* `in-progress`) is not `ready`: `ready` requires `state === "open"`, the
|
|
20
|
-
* same "already met/already running leaves the queue" rule
|
|
21
|
-
* states for a contract that is itself met.
|
|
19
|
+
* same "already met/already running leaves the queue" rule the reference
|
|
20
|
+
* rule states for a contract that is itself met.
|
|
22
21
|
*
|
|
23
|
-
* The rules
|
|
24
|
-
* (memberState's draft/failing cases), re-checking that a `met`
|
|
25
|
-
* newest evidence still passes, and refusing a contract while a
|
|
26
|
-
* against it -- have no equivalent in a `needs` link. The full
|
|
27
|
-
* every
|
|
22
|
+
* The rules the reference rule adds on top -- approval before a dependency
|
|
23
|
+
* counts (memberState's draft/failing cases), re-checking that a `met`
|
|
24
|
+
* dependency's newest evidence still passes, and refusing a contract while a
|
|
25
|
+
* unit is open against it -- have no equivalent in a `needs` link. The full
|
|
26
|
+
* table of every rule and its home (a `needs` link, the runner, or "not
|
|
28
27
|
* applicable") is on INTENTIUS/chant#2734.
|
|
29
28
|
*
|
|
30
|
-
* This fixture mirrors a small
|
|
31
|
-
* `queryRecords`'s `ready`/`blockedBy` against a minimal, local
|
|
32
|
-
* re-implementation of the expressible subset of
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* mismatch.
|
|
29
|
+
* This fixture mirrors a small reference contract graph as work items and
|
|
30
|
+
* checks `queryRecords`'s `ready`/`blockedBy` against a minimal, local
|
|
31
|
+
* re-implementation of the expressible subset of that rule (met depends_on,
|
|
32
|
+
* self not already met), so a regression in either the work kind's readiness
|
|
33
|
+
* formula or this reading of the reference rule shows up as a mismatch.
|
|
36
34
|
*/
|
|
37
35
|
|
|
38
36
|
import { readFileSync } from "node:fs";
|
|
@@ -63,13 +61,13 @@ function work(id: string, fields: Record<string, unknown>): string {
|
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
/**
|
|
66
|
-
* The expressible subset of
|
|
64
|
+
* The expressible subset of the reference rule, on a graph shaped like
|
|
67
65
|
* `depends_on`: ready once the item is `open` (not already met, not already
|
|
68
|
-
* running) and every dependency is `done` (
|
|
69
|
-
* is `dropped` never becomes done, so it blocks forever --
|
|
70
|
-
*
|
|
66
|
+
* running) and every dependency is `done` (the reference rule's "met"). A
|
|
67
|
+
* dependency that is `dropped` never becomes done, so it blocks forever --
|
|
68
|
+
* the same effect the reference rule gets from a retired dependency.
|
|
71
69
|
*/
|
|
72
|
-
function
|
|
70
|
+
function referenceReadyIsh(items: { id: string; state: string; needs: string[] }[]): { ready: Set<string>; blockedBy: Map<string, string[]> } {
|
|
73
71
|
const stateOf = new Map(items.map((i) => [i.id, i.state]));
|
|
74
72
|
const ready = new Set<string>();
|
|
75
73
|
const blockedBy = new Map<string, string[]>();
|
|
@@ -90,13 +88,13 @@ beforeAll(() => {
|
|
|
90
88
|
"decisions/decision.schema.json": readFileSync(join(REF, "decisions", "decision.schema.json"), "utf-8"),
|
|
91
89
|
"work/work.kind.mjs": readFileSync(join(REF, "work", "work.kind.mjs"), "utf-8"),
|
|
92
90
|
"work/work.schema.json": readFileSync(join(REF, "work", "work.schema.json"), "utf-8"),
|
|
93
|
-
// W-001: already done, so it leaves the ready queue --
|
|
94
|
-
// contract that is itself met is done, not ready".
|
|
91
|
+
// W-001: already done, so it leaves the ready queue -- the reference
|
|
92
|
+
// rule's "a contract that is itself met is done, not ready".
|
|
95
93
|
"work/W-001-base.md": work("W-001", { state: "done", closed_on: "2026-09-24", evidence: [{ title: "shipped", url: "https://example.com/w1" }] }),
|
|
96
94
|
// W-002: needs W-001, which is done -- both rules agree this is ready.
|
|
97
95
|
"work/W-002-after-base.md": work("W-002", { needs: ["W-001"] }),
|
|
98
96
|
// W-003: needs W-004, which is dropped and so never turns done -- blocked
|
|
99
|
-
// forever, the same as a retired dependency in
|
|
97
|
+
// forever, the same as a retired dependency in the reference rule.
|
|
100
98
|
"work/W-003-needs-dropped.md": work("W-003", { needs: ["W-004"] }),
|
|
101
99
|
"work/W-004-dropped.md": work("W-004", { state: "dropped", closed_on: "2026-09-24" }),
|
|
102
100
|
// W-005: needs W-002, which is open (not done yet) -- blocked.
|
|
@@ -111,22 +109,22 @@ beforeAll(() => {
|
|
|
111
109
|
});
|
|
112
110
|
afterAll(cleanScratch);
|
|
113
111
|
|
|
114
|
-
describe("work kind ready/blockedBy vs.
|
|
112
|
+
describe("work kind ready/blockedBy vs. a reference ready-queue rule, expressible rules (#2734)", () => {
|
|
115
113
|
test("matches on every item, for the rules a needs link can express", async () => {
|
|
116
114
|
const doc = await queryRecords({ kind: WORK, cwd: root });
|
|
117
115
|
if ("error" in doc) throw new Error(doc.error.message);
|
|
118
116
|
expect(doc.summary).toEqual({ total: 7, valid: 7, invalid: 0, superseded: 0 });
|
|
119
117
|
|
|
120
118
|
const items = doc.records.map((r) => ({ id: r.id!, state: r.state!, needs: (r.data as { needs?: string[] }).needs ?? [] }));
|
|
121
|
-
const
|
|
119
|
+
const reference = referenceReadyIsh(items);
|
|
122
120
|
|
|
123
121
|
for (const r of doc.records) {
|
|
124
122
|
const id = r.id!;
|
|
125
|
-
expect(r.ready, `${id}.ready`).toBe(
|
|
123
|
+
expect(r.ready, `${id}.ready`).toBe(reference.ready.has(id));
|
|
126
124
|
expect(
|
|
127
125
|
(r.blockedBy ?? []).map((b) => b.id),
|
|
128
126
|
`${id}.blockedBy`,
|
|
129
|
-
).toEqual(
|
|
127
|
+
).toEqual(reference.blockedBy.get(id));
|
|
130
128
|
}
|
|
131
129
|
});
|
|
132
130
|
|
|
@@ -32,6 +32,7 @@ import { queryRecords } from "./records-cli";
|
|
|
32
32
|
import { amendRecord, newRecord, resolveWriteKind, reviewRecord } from "./records-write";
|
|
33
33
|
import recordsSchema from "./records.schema.json";
|
|
34
34
|
import { WORK_WARNING_CODES } from "./work";
|
|
35
|
+
import { CHANGES_FINDING_CODES } from "./changes";
|
|
35
36
|
|
|
36
37
|
const REF = join(REPO, "reference-workspace");
|
|
37
38
|
const DECISION = (() => {
|
|
@@ -227,9 +228,9 @@ describe("work records on read (#2683)", () => {
|
|
|
227
228
|
expect("error" in doc && doc.error.code).toBe("kind-invalid");
|
|
228
229
|
});
|
|
229
230
|
|
|
230
|
-
test("the reference work schema's finding codes are the intent graph's", () => {
|
|
231
|
+
test("the reference work schema's finding codes are the intent graph's and the change check's (#2773)", () => {
|
|
231
232
|
const schema = JSON.parse(readFileSync(join(REF, "work", "work.schema.json"), "utf-8")) as { definitions: { sourceGap: { properties: { finding: { anyOf: [{ enum: string[] }] } } } } };
|
|
232
|
-
expect(schema.definitions.sourceGap.properties.finding.anyOf[0].enum).toEqual([...INTENT_FINDING_CODES]);
|
|
233
|
+
expect(schema.definitions.sourceGap.properties.finding.anyOf[0].enum).toEqual([...INTENT_FINDING_CODES, ...CHANGES_FINDING_CODES]);
|
|
233
234
|
});
|
|
234
235
|
});
|
|
235
236
|
|
|
@@ -369,6 +370,13 @@ describe("writing work items on a copy of the reference workspace (#2683)", () =
|
|
|
369
370
|
expect("error" in doc && doc.error.code).toBe("record-schema-invalid");
|
|
370
371
|
});
|
|
371
372
|
|
|
373
|
+
test("records new refuses an ask source with no said or by (#2851)", async () => {
|
|
374
|
+
const noSaid = await newRecord({ kind: "work/work.kind.mjs", fields: fresh({ source: { ask: { by: "morgan", at: "2026-09-25T10:00:00Z", via: "hud" } } }), dryRun: true, cwd: copy });
|
|
375
|
+
expect("error" in noSaid && noSaid.error.code).toBe("record-schema-invalid");
|
|
376
|
+
const noBy = await newRecord({ kind: "work/work.kind.mjs", fields: fresh({ source: { ask: { said: "Can we add a dark mode toggle?", at: "2026-09-25T10:00:00Z", via: "hud" } } }), dryRun: true, cwd: copy });
|
|
377
|
+
expect("error" in noBy && noBy.error.code).toBe("record-schema-invalid");
|
|
378
|
+
});
|
|
379
|
+
|
|
372
380
|
test("records new with a fresh prefix takes --prefix W", async () => {
|
|
373
381
|
const doc = await newRecord({ kind: "work/work.kind.mjs", fields: fresh(), prefix: "W", dryRun: true, cwd: copy });
|
|
374
382
|
expect(doc).toMatchObject({ id: "W-004" });
|
|
@@ -387,4 +395,14 @@ describe("writing work items on a copy of the reference workspace (#2683)", () =
|
|
|
387
395
|
const doc = await reviewRecord({ kind: "work/work.kind.mjs", id: "W-003", verdict: "agree", by: "alice", cwd: copy });
|
|
388
396
|
expect("error" in doc && doc.error.code).toBe("review-unsupported");
|
|
389
397
|
});
|
|
398
|
+
|
|
399
|
+
test("records new with an ask source round-trips through records --json (#2851)", async () => {
|
|
400
|
+
const ask = { said: "Can we add a dark mode toggle to the wireframe?", by: "morgan", at: "2026-09-25T10:00:00Z", via: "hud", session: "hud-9f2" };
|
|
401
|
+
const doc = await newRecord({ kind: resolveWriteKind("work", copy), fields: fresh({ source: { ask } }), cwd: copy });
|
|
402
|
+
if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
|
|
403
|
+
const read = await queryRecords({ kind: "work/work.kind.mjs", cwd: copy });
|
|
404
|
+
records.expectValid(read);
|
|
405
|
+
if ("error" in read) throw new Error(read.error.message);
|
|
406
|
+
expect(read.records.find((r) => r.id === doc.id)).toMatchObject({ valid: true, data: { source: { ask } } });
|
|
407
|
+
});
|
|
390
408
|
});
|