@intentius/chant 0.90.0 → 0.93.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/components.d.ts +8 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +20 -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-tools.d.ts +7 -4
- 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 +42 -1
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/components/verbs/vuln-scan.d.ts +72 -0
- package/dist/components/verbs/vuln-scan.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/git.d.ts +40 -5
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +72 -16
- package/dist/lifecycle/lease.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts +3 -2
- package/dist/lifecycle/member-ledger.d.ts.map +1 -1
- package/dist/lifecycle/plan-ledger.d.ts +114 -0
- package/dist/lifecycle/plan-ledger.d.ts.map +1 -0
- 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/lifecycle/work-lease.d.ts +140 -0
- package/dist/lifecycle/work-lease.d.ts.map +1 -0
- 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 +203 -2
- 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/discover.d.ts +25 -0
- package/dist/op/discover.d.ts.map +1 -1
- 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 +18 -5
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/lifecycle-receipt-store.d.ts +34 -0
- package/dist/op/lifecycle-receipt-store.d.ts.map +1 -0
- package/dist/op/local-executor.d.ts +48 -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 +12 -1
- 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 +126 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +46 -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/step-output-ref.d.ts +2 -2
- package/dist/op/step-output-ref.d.ts.map +1 -1
- 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 +161 -0
- package/dist/op/steward.d.ts.map +1 -0
- package/dist/op/types.d.ts +58 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-decl.d.ts +18 -0
- package/dist/op/work-lease-decl.d.ts.map +1 -0
- package/dist/op/work-lease-run.d.ts +173 -0
- package/dist/op/work-lease-run.d.ts.map +1 -0
- 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-isolation.d.ts +99 -0
- package/dist/workspace/box-isolation.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 +99 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -0
- package/dist/workspace/checks/box-isolation.d.ts +18 -0
- package/dist/workspace/checks/box-isolation.d.ts.map +1 -0
- package/dist/workspace/checks/boxes.d.ts +104 -0
- package/dist/workspace/checks/boxes.d.ts.map +1 -0
- 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 +8 -0
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +13 -1
- 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 +229 -0
- package/dist/workspace/decide.d.ts.map +1 -0
- package/dist/workspace/decision-points.schema.json +137 -0
- package/dist/workspace/declaration.d.ts +103 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +285 -0
- package/dist/workspace/declared-kinds.d.ts +32 -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.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 +113 -0
- package/dist/workspace/points-cli.d.ts.map +1 -0
- package/dist/workspace/points.d.ts +320 -0
- package/dist/workspace/points.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +38 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +23 -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 +42 -6
- 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 +179 -0
- package/dist/workspace/status-stewards.d.ts.map +1 -0
- package/dist/workspace/status.d.ts +68 -1
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +189 -0
- package/dist/workspace/work-cli.d.ts.map +1 -0
- 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/components.test.ts +93 -0
- package/src/cli/handlers/components.ts +44 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +16 -2
- package/src/cli/handlers/operator.ts +141 -4
- package/src/cli/handlers/run.test.ts +173 -2
- package/src/cli/handlers/run.ts +174 -3
- package/src/cli/main.test.ts +50 -0
- package/src/cli/main.ts +97 -2
- package/src/cli/mcp/workspace-tools.test.ts +48 -19
- package/src/cli/mcp/workspace-tools.ts +110 -8
- package/src/cli/plugins.ts +11 -3
- package/src/cli/registry.ts +42 -1
- 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/components/verbs/vuln-scan.test.ts +124 -1
- package/src/components/verbs/vuln-scan.ts +142 -1
- 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/git.ts +65 -15
- package/src/lifecycle/lease.test.ts +22 -0
- package/src/lifecycle/lease.ts +133 -29
- package/src/lifecycle/member-ledger.ts +3 -2
- package/src/lifecycle/plan-ledger.test.ts +148 -0
- package/src/lifecycle/plan-ledger.ts +158 -0
- package/src/lifecycle/run-ledger.ts +6 -0
- package/src/lifecycle/symptoms.ts +95 -0
- package/src/lifecycle/work-lease.test.ts +236 -0
- package/src/lifecycle/work-lease.ts +426 -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 +300 -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 +124 -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/discover.ts +71 -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 +34 -6
- package/src/op/lifecycle-receipt-store.test.ts +60 -0
- package/src/op/lifecycle-receipt-store.ts +61 -0
- package/src/op/local-executor.ts +367 -33
- package/src/op/local-output.test.ts +33 -0
- package/src/op/local-output.ts +33 -2
- package/src/op/op-ir.test.ts +10 -0
- package/src/op/op-ir.ts +18 -0
- package/src/op/op-verb-class.ts +1 -0
- package/src/op/operator.ts +360 -10
- package/src/op/runtime.ts +37 -8
- package/src/op/runtimes/local.ts +6 -1
- package/src/op/source-archive.ts +161 -0
- package/src/op/step-output-ref.ts +6 -2
- package/src/op/steward-points.test.ts +313 -0
- package/src/op/steward-points.ts +194 -0
- package/src/op/steward-turn.ts +79 -0
- package/src/op/steward.test.ts +296 -0
- package/src/op/steward.ts +277 -0
- package/src/op/types.ts +60 -0
- package/src/op/work-lease-decl.ts +80 -0
- package/src/op/work-lease-run.test.ts +345 -0
- package/src/op/work-lease-run.ts +395 -0
- 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-isolation.test.ts +261 -0
- package/src/workspace/box-isolation.ts +205 -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 +164 -0
- package/src/workspace/check-contract.test.ts +5 -1
- package/src/workspace/check.schema.json +25 -7
- package/src/workspace/checks/box-isolation.ts +68 -0
- package/src/workspace/checks/boxes.test.ts +276 -0
- package/src/workspace/checks/boxes.ts +442 -0
- package/src/workspace/checks/diagrams.test.ts +134 -0
- package/src/workspace/checks/diagrams.ts +108 -0
- package/src/workspace/checks/records.ts +72 -0
- package/src/workspace/checks.test.ts +7 -0
- package/src/workspace/checks.ts +27 -3
- 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 +224 -0
- package/src/workspace/decide.ts +709 -0
- package/src/workspace/decision-points.schema.json +137 -0
- package/src/workspace/declaration.schema.json +285 -0
- package/src/workspace/declaration.test.ts +58 -0
- package/src/workspace/declaration.ts +241 -0
- package/src/workspace/declared-kinds.test.ts +5 -5
- package/src/workspace/declared-kinds.ts +71 -3
- 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.schema.json +12 -3
- 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/point-answer.schema.json +95 -0
- package/src/workspace/points-cli.ts +284 -0
- package/src/workspace/points-write.schema.json +499 -0
- package/src/workspace/points.schema.json +743 -0
- package/src/workspace/points.test.ts +264 -0
- package/src/workspace/points.ts +564 -0
- package/src/workspace/read-contract.test.ts +18 -1
- package/src/workspace/reason-codes.test.ts +24 -1
- package/src/workspace/reason-codes.ts +49 -0
- package/src/workspace/record-assets.test.ts +3 -2
- package/src/workspace/record-assets.ts +4 -1
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-cli.ts +28 -5
- package/src/workspace/records-close.schema.json +2 -0
- package/src/workspace/records-contract.test.ts +3 -2
- 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-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 +53 -2
- package/src/workspace/records.test.ts +48 -3
- package/src/workspace/records.ts +142 -6
- package/src/workspace/status-contract.test.ts +189 -6
- package/src/workspace/status-gates.ts +9 -4
- package/src/workspace/status-stewards.ts +329 -0
- package/src/workspace/status.schema.json +335 -5
- package/src/workspace/status.ts +142 -6
- package/src/workspace/work-acceptance.test.ts +274 -0
- package/src/workspace/work-cli.e2e.test.ts +180 -0
- package/src/workspace/work-cli.ts +429 -0
- 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-lease.schema.json +233 -0
- package/src/workspace/work-readiness-chud.test.ts +145 -0
- package/src/workspace/work.test.ts +3 -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
|
@@ -14,11 +14,21 @@
|
|
|
14
14
|
* | WSP113 | a pinned file that a superseding record pins at the old hash: the artifact did not follow the decision |
|
|
15
15
|
* | WSP114 | the records of `--kind` can't be read (fixed) |
|
|
16
16
|
* | WSP115 | a record kind the declaration names is missing or does not load as one (fixed, #2680) |
|
|
17
|
+
* | WSP116 | an answer kind's decision points file is missing or not valid (fixed, ws-058, #2738) |
|
|
18
|
+
* | WSP117 | a done work item has an acceptance criterion no passing evidence meets (#2772) |
|
|
19
|
+
*
|
|
20
|
+
* WSP117 reads the records of every declared work kind with acceptance
|
|
21
|
+
* criteria, in the working tree, and the records of `--kind`. It carries the
|
|
22
|
+
* reason code `work-acceptance-unmet`.
|
|
17
23
|
*/
|
|
18
24
|
|
|
19
25
|
import type { WorkspaceCheck, WorkspaceDiagnostic } from "../checks";
|
|
26
|
+
import type { ReasonCode } from "../reason-codes";
|
|
20
27
|
import type { ReadErrorCode, RecordEntry } from "../records";
|
|
21
28
|
|
|
29
|
+
/** The read contract's codes the record checks carry as `code`: WSP117's (#2772). */
|
|
30
|
+
export const RECORD_FINDING_CODES = ["work-acceptance-unmet"] as const satisfies readonly ReasonCode[];
|
|
31
|
+
|
|
22
32
|
/** The records `check --kind` read, or why they could not be read. */
|
|
23
33
|
export type RecordFacts =
|
|
24
34
|
| { kind: string; records: readonly (RecordEntry & { file: string })[] }
|
|
@@ -99,4 +109,66 @@ export const RECORD_CHECKS: readonly WorkspaceCheck[] = [
|
|
|
99
109
|
}));
|
|
100
110
|
},
|
|
101
111
|
},
|
|
112
|
+
{
|
|
113
|
+
id: "WSP116",
|
|
114
|
+
name: "decision-points-invalid",
|
|
115
|
+
description:
|
|
116
|
+
"Every answer kind the declaration names has a decision points file that matches decision-points.schema.json: each input names a read-contract output, each table row tests declared inputs and answers a candidate, each model id is pinned, and the chain ends in its one quorum (ws-058).",
|
|
117
|
+
severity: "error",
|
|
118
|
+
configurable: false,
|
|
119
|
+
check(ctx) {
|
|
120
|
+
const out: WorkspaceDiagnostic[] = [];
|
|
121
|
+
for (const k of ctx.facts?.declaredKinds ?? []) {
|
|
122
|
+
if (!k.points) continue;
|
|
123
|
+
const where = k.declared.member === null ? "the workspace" : `member ${k.declared.member}`;
|
|
124
|
+
const finding = (message: string) => ({
|
|
125
|
+
checkId: this.id,
|
|
126
|
+
severity: this.severity,
|
|
127
|
+
message,
|
|
128
|
+
...(k.declared.member !== null ? { entity: k.declared.member } : {}),
|
|
129
|
+
pointer: `${k.declared.pointer}/kind`,
|
|
130
|
+
});
|
|
131
|
+
for (const p of k.points.problems) out.push(finding(`${where} declares the answer kind ${k.declared.kind}, whose points file ${k.points.file} ${p.field ? `has ${p.field}, which ${p.message}` : p.message}`));
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "WSP117",
|
|
138
|
+
name: "work-acceptance-unmet",
|
|
139
|
+
description:
|
|
140
|
+
"Every done work item meets its acceptance criteria: each has evidence that names it, passed, and has the verification the criterion expects, and a manual verdict is from someone other than the implementer (#2772).",
|
|
141
|
+
severity: "error",
|
|
142
|
+
configurable: true,
|
|
143
|
+
check(ctx) {
|
|
144
|
+
const out: WorkspaceDiagnostic[] = [];
|
|
145
|
+
const seen = new Set<string>();
|
|
146
|
+
const facts = ctx.facts?.records;
|
|
147
|
+
if (facts && !("error" in facts)) {
|
|
148
|
+
for (const r of facts.records) {
|
|
149
|
+
if (r.supersededBy !== null) continue;
|
|
150
|
+
const w = r.warnings.find((x) => x.code === "work-acceptance-unmet");
|
|
151
|
+
if (!w || seen.has(r.file)) continue;
|
|
152
|
+
seen.add(r.file);
|
|
153
|
+
out.push({ checkId: this.id, severity: this.severity, message: w.message, pointer: "", file: r.file, code: "work-acceptance-unmet" });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const k of ctx.facts?.declaredKinds ?? []) {
|
|
157
|
+
for (const a of k.acceptance ?? []) {
|
|
158
|
+
if (a.unmet === null || seen.has(a.file)) continue;
|
|
159
|
+
seen.add(a.file);
|
|
160
|
+
out.push({
|
|
161
|
+
checkId: this.id,
|
|
162
|
+
severity: this.severity,
|
|
163
|
+
message: a.unmet,
|
|
164
|
+
...(k.declared.member !== null ? { entity: k.declared.member } : {}),
|
|
165
|
+
pointer: "",
|
|
166
|
+
file: a.file,
|
|
167
|
+
code: "work-acceptance-unmet",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
},
|
|
173
|
+
},
|
|
102
174
|
];
|
|
@@ -60,6 +60,13 @@ describe("the WSP catalog", () => {
|
|
|
60
60
|
"records-unreadable",
|
|
61
61
|
// A declared record kind that doesn't load breaks every reader's default (#2680).
|
|
62
62
|
"record-kind-unloadable",
|
|
63
|
+
// An answer kind's points file that isn't valid can't be asked (ws-058).
|
|
64
|
+
"decision-points-invalid",
|
|
65
|
+
// A credential in a box's files is in git (#2726).
|
|
66
|
+
"box-credential-declared",
|
|
67
|
+
// Two boxes sharing a value, or a box that only holds on one machine, is never the fix (#2727).
|
|
68
|
+
"box-isolation-collision",
|
|
69
|
+
"box-isolation-literal",
|
|
63
70
|
]);
|
|
64
71
|
});
|
|
65
72
|
|
package/src/workspace/checks.ts
CHANGED
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
* | WSP101 to WSP106 | generated files |
|
|
29
29
|
* | WSP111 to WSP114 | records read with `--kind` (#2549) |
|
|
30
30
|
* | WSP115 | the record kinds the declaration names (#2680) |
|
|
31
|
+
* | WSP116 | the decision points file of each declared answer kind (ws-058) |
|
|
32
|
+
* | WSP117 | a done work item's acceptance criteria all met (#2772) |
|
|
33
|
+
* | WSP121, WSP122 | boxes: no literal credential, every capability brokered (#2726) |
|
|
34
|
+
* | WSP123, WSP124 | box isolation: no shared port, state path or cookie, no literal machine path (#2727) |
|
|
35
|
+
* | WSP125 | boxes: a fountain Box declares the callback token fountain gives its sandbox (#2780) |
|
|
36
|
+
* | WSP131 to WSP133 | diagram artifacts: source and render exist, a recorded source hash still matches (#2764) |
|
|
31
37
|
*/
|
|
32
38
|
|
|
33
39
|
import { realpathSync } from "node:fs";
|
|
@@ -45,6 +51,8 @@ import { gatherGeneratedFacts, GENERATED_CHECKS, type GeneratedFileFacts } from
|
|
|
45
51
|
import { gatherLedgerFacts, LEDGER_CHECKS, type MemberLedgerFacts } from "./checks/ledgers";
|
|
46
52
|
import { gatherPipelineFacts, PIPELINE_CHECKS, type MemberPipelineFacts } from "./checks/pipelines";
|
|
47
53
|
import { RECORD_CHECKS, type RecordFacts } from "./checks/records";
|
|
54
|
+
import { BOX_CHECKS } from "./checks/boxes";
|
|
55
|
+
import { DIAGRAM_CHECKS } from "./checks/diagrams";
|
|
48
56
|
import { loadDeclaredKinds, type DeclaredKind } from "./declared-kinds";
|
|
49
57
|
|
|
50
58
|
/**
|
|
@@ -87,6 +95,12 @@ export interface WorkspaceDiagnostic extends PostSynthDiagnostic {
|
|
|
87
95
|
pointer: string;
|
|
88
96
|
/** An absolute path, for a finding about a file other than the declaration, such as a record (#2549). It is reported at its first line. */
|
|
89
97
|
file?: string;
|
|
98
|
+
/** A path in the tree checked, relative to the workspace root, for a finding about a file there (#2726). It is reported at `line` and `column`. */
|
|
99
|
+
treeFile?: string;
|
|
100
|
+
line?: number;
|
|
101
|
+
column?: number;
|
|
102
|
+
/** The read contract's code for the finding, when it has one (#2726). */
|
|
103
|
+
code?: ReasonCode;
|
|
90
104
|
}
|
|
91
105
|
|
|
92
106
|
export interface WorkspaceCheck {
|
|
@@ -104,7 +118,7 @@ export interface WorkspaceCheck {
|
|
|
104
118
|
/** A diagnostic ready for lint's reporters, with the entry it is about. */
|
|
105
119
|
export interface WorkspaceFinding extends LintDiagnostic {
|
|
106
120
|
entity?: string;
|
|
107
|
-
/** The read contract's reason code
|
|
121
|
+
/** The read contract's reason code: for a WSP001 finding, why the declaration can't be read (#2536); for a box check, its finding code (#2726). */
|
|
108
122
|
code?: ReasonCode;
|
|
109
123
|
}
|
|
110
124
|
|
|
@@ -307,6 +321,10 @@ export const WORKSPACE_CHECKS: readonly WorkspaceCheck[] = [
|
|
|
307
321
|
...GENERATED_CHECKS,
|
|
308
322
|
// Records read with --kind (#2549).
|
|
309
323
|
...RECORD_CHECKS,
|
|
324
|
+
// Boxes and their brokered capabilities (#2726).
|
|
325
|
+
...BOX_CHECKS,
|
|
326
|
+
// Diagram artifacts (#2764).
|
|
327
|
+
...DIAGRAM_CHECKS,
|
|
310
328
|
];
|
|
311
329
|
|
|
312
330
|
const BY_ID = new Map(WORKSPACE_CHECKS.map((c) => [c.id, c]));
|
|
@@ -449,7 +467,8 @@ export async function runDeclarationChecks(
|
|
|
449
467
|
const { registry, problems } = loadKindRegistry(declaration.pins, root);
|
|
450
468
|
const gathered = options.gather === false || options.tree ? {} : await gatherWorkspaceFacts(root, declaration, options);
|
|
451
469
|
// The declared record kinds load from the working tree; under --at only whether each exists at the revision is checked (#2680).
|
|
452
|
-
|
|
470
|
+
// A declared work kind's acceptance criteria are counted in the working tree too (#2772).
|
|
471
|
+
const declaredKinds = options.gather === false ? undefined : await loadDeclaredKinds(declaration, tree, root, { load: !options.tree, acceptance: !options.tree });
|
|
453
472
|
const facts: WorkspaceFacts = { ...gathered, ...(options.records ? { records: options.records } : {}), ...(declaredKinds ? { declaredKinds } : {}) };
|
|
454
473
|
const ctx: WorkspaceCheckContext = { declaration, tree, groups, kinds: registry, kindProblems: problems, facts };
|
|
455
474
|
const findings = runWorkspaceChecks(ctx);
|
|
@@ -459,11 +478,16 @@ export async function runDeclarationChecks(
|
|
|
459
478
|
const locate = (pointer: string): TextLocation => (parsed.ok ? parsed.locate(pointer) : { line: 1, column: 1 });
|
|
460
479
|
const file = display(declaration.file);
|
|
461
480
|
const toFinding = (d: WorkspaceDiagnostic): WorkspaceFinding => ({
|
|
462
|
-
...(d.
|
|
481
|
+
...(d.treeFile !== undefined
|
|
482
|
+
? { file: display(d.treeFile), line: d.line ?? 1, column: d.column ?? 1 }
|
|
483
|
+
: d.file !== undefined
|
|
484
|
+
? { file: display(relative(root, d.file).split(sep).join("/")), line: 1, column: 1 }
|
|
485
|
+
: { file, ...locate(d.pointer) }),
|
|
463
486
|
ruleId: d.checkId,
|
|
464
487
|
severity: d.severity,
|
|
465
488
|
message: d.message,
|
|
466
489
|
...(d.entity !== undefined ? { entity: d.entity } : {}),
|
|
490
|
+
...(d.code !== undefined ? { code: d.code } : {}),
|
|
467
491
|
});
|
|
468
492
|
const order = (a: LintDiagnostic, b: LintDiagnostic) => a.line - b.line || a.column - b.column || (a.ruleId < b.ruleId ? -1 : a.ruleId > b.ruleId ? 1 : 0);
|
|
469
493
|
const diagnostics = active.map(toFinding).sort(order);
|
|
@@ -113,6 +113,8 @@ export interface GraphRecord {
|
|
|
113
113
|
state: string | null;
|
|
114
114
|
valid: boolean;
|
|
115
115
|
supersededBy: string | null;
|
|
116
|
+
/** The ids of records whose remediates link names this one (#2774). */
|
|
117
|
+
remediatedBy: string[];
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
/** Read one member's `chant graph --format ir` output, upgrading an unversioned (v1) IR in place. */
|
|
@@ -17,6 +17,9 @@ export const recordKind = {
|
|
|
17
17
|
// Sealed once reached (#2555).
|
|
18
18
|
closedStates: ["ratified", "superseded"],
|
|
19
19
|
supersedes: { field: "supersedes", key: "decision" },
|
|
20
|
+
// A remediates link names a closed decision this one fixes the consequence
|
|
21
|
+
// of, without replacing it: the target's state never changes (#2774).
|
|
22
|
+
remediates: { field: "remediates", key: "decision" },
|
|
20
23
|
// A supersedes link takes effect under an equal or stricter approval rule
|
|
21
24
|
// (#2524 D4): from a record ranked above 0 and at least as high as the one it
|
|
22
25
|
// names. A decided record supersedes a decided or proposed one, a ratified
|
|
@@ -26,9 +29,16 @@ export const recordKind = {
|
|
|
26
29
|
pins: { field: "evidence" },
|
|
27
30
|
// member:<name> and path:<path> entries are the record's links in workspace graph (#2549).
|
|
28
31
|
constrains: { field: "constrains" },
|
|
32
|
+
// Workspace paths a change carrying the decision out must not touch
|
|
33
|
+
// (#2773): chant workspace check --changes reports change-out-of-scope.
|
|
34
|
+
outOfScope: { field: "out_of_scope" },
|
|
29
35
|
// Verdicts, and the field naming the decider, for each record's digest and
|
|
30
36
|
// quorum (#2671, #2672).
|
|
31
37
|
reviews: { field: "reviews", decider: "decided_by" },
|
|
38
|
+
// records new --by and the MCP records-new tool's by name a proposal's
|
|
39
|
+
// proposer here, apart from decided_by, which stays null until the
|
|
40
|
+
// decision is decided (#2756).
|
|
41
|
+
proposedBy: { field: "proposed_by" },
|
|
32
42
|
// source is where a decision came from, and says where its proposal came
|
|
33
43
|
// from too: via, client, harness, model, session, turns and a transcript
|
|
34
44
|
// pinned by hash (#2708).
|
|
@@ -116,6 +116,19 @@
|
|
|
116
116
|
]
|
|
117
117
|
}
|
|
118
118
|
},
|
|
119
|
+
"remediates": {
|
|
120
|
+
"description": "Closed decisions this one fixes the consequence of, without replacing them: each names the decision by id and may say why (#2774). The named decision's status never changes because of a remediates link; a decision still open is amended instead, never remediated. Optional: most decisions remediate nothing.",
|
|
121
|
+
"type": "array",
|
|
122
|
+
"items": {
|
|
123
|
+
"type": "object",
|
|
124
|
+
"additionalProperties": false,
|
|
125
|
+
"required": ["decision"],
|
|
126
|
+
"properties": {
|
|
127
|
+
"decision": { "$ref": "#/definitions/decisionId" },
|
|
128
|
+
"reason": { "type": ["string", "null"] }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
119
132
|
"evidence": {
|
|
120
133
|
"description": "The research, audits, design sections and workspace files behind the decision. A public link carries a url; a file in the workspace carries a path and the hash of its bytes (#2549). May be empty when the decision cites nothing; chant workspace records then warns decision-no-evidence (#2654).",
|
|
121
134
|
"type": "array",
|
|
@@ -127,6 +140,10 @@
|
|
|
127
140
|
"description": "Who chose. A forge login. Null in state proposed or withdrawn.",
|
|
128
141
|
"type": ["string", "null"]
|
|
129
142
|
},
|
|
143
|
+
"proposed_by": {
|
|
144
|
+
"description": "Who proposed this decision: the name chant workspace records new --by or the MCP records-new tool's by wrote, for a decision that opened proposed (#2756). Null or absent when nobody named a proposer, or the decision opened straight into a later state. Never cleared by a later decide, ratify or supersede.",
|
|
145
|
+
"type": ["string", "null"]
|
|
146
|
+
},
|
|
130
147
|
"decided_on": {
|
|
131
148
|
"description": "When the choice was made, as YYYY-MM-DD. Quoted, so YAML reads it as a string.",
|
|
132
149
|
"type": ["string", "null"],
|
|
@@ -218,6 +235,15 @@
|
|
|
218
235
|
"type": "string",
|
|
219
236
|
"pattern": "^([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#[0-9]+|[a-z][a-z0-9]{0,15}-[0-9]{3,}|member:[a-z0-9][a-z0-9-]{0,39}|path:(?!/)(?!(?:[^/]*/)*\\.{1,2}(?:/|$))(?!.*//)[^\\\\\\u0000-\\u001f]*[^/\\\\\\u0000-\\u001f])$"
|
|
220
237
|
}
|
|
238
|
+
},
|
|
239
|
+
"out_of_scope": {
|
|
240
|
+
"description": "Workspace paths this decision puts out of scope (#2773): files or directories, from the workspace root, that a change carrying out the decision must not touch. chant workspace check --changes reports a change to one as change-out-of-scope while the decision is in hand, and the decision does not cover it even where its constrains would. Optional.",
|
|
241
|
+
"type": "array",
|
|
242
|
+
"items": {
|
|
243
|
+
"type": "string",
|
|
244
|
+
"pattern": "^(?!/)(?!(?:[^/]*/)*\\.{1,2}(?:/|$))(?!.*//)[^\\\\\\u0000-\\u001f]*[^/\\\\\\u0000-\\u001f]$"
|
|
245
|
+
},
|
|
246
|
+
"uniqueItems": true
|
|
221
247
|
}
|
|
222
248
|
},
|
|
223
249
|
"allOf": [
|
|
@@ -346,6 +346,9 @@ export function mcpToolCall(argv: readonly string[]): { name: string; arguments:
|
|
|
346
346
|
else if (a === "--since") args.since = value();
|
|
347
347
|
else if (a === "--compare-to") args.compareTo = value();
|
|
348
348
|
else if (a === "--intent") args.intent = value();
|
|
349
|
+
else if (a === "--changes") args.range = value();
|
|
350
|
+
else if (a === "--work") args.work = value();
|
|
351
|
+
else if (a === "--severity") args.severity = value();
|
|
349
352
|
else if (a === "--current") args.current = true;
|
|
350
353
|
else if (a === "--composites") args.composites = true;
|
|
351
354
|
else if (a.startsWith("-")) return undefined;
|
|
@@ -358,6 +361,8 @@ export function mcpToolCall(argv: readonly string[]): { name: string; arguments:
|
|
|
358
361
|
return positional.length === 1 && kinds.length === 0 ? { name: "workspace-status", arguments: { ...args, env: positional[0] } } : undefined;
|
|
359
362
|
case "records":
|
|
360
363
|
return positional.length === 0 && kinds.length <= 1 ? { name: "workspace-records", arguments: { ...args, ...(kinds.length ? { kind: kinds[0] } : {}) } } : undefined;
|
|
364
|
+
case "check":
|
|
365
|
+
return positional.length === 0 && args.range !== undefined ? { name: "workspace-changes", arguments: { ...args, ...(kinds.length ? { kind: kinds } : {}) } } : undefined;
|
|
361
366
|
case "graph":
|
|
362
367
|
return positional.length === 0 ? { name: "workspace-graph", arguments: { ...args, ...(kinds.length ? { kind: kinds } : {}) } } : undefined;
|
|
363
368
|
default:
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Answers to decision points as records (ws-058, #2739), with a stub model
|
|
3
|
+
* decider, on a workspace in a throwaway git repository: `points ask` and
|
|
4
|
+
* `points answer` as `decide.ts` runs them, `points --json` as
|
|
5
|
+
* `points-cli.ts` prints it, and WSP116 in `check`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { cpSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
11
|
+
import { runDeclarationChecks } from "./checks";
|
|
12
|
+
import { cleanScratch, commitAll, contract, REPO, repo } from "./__fixtures__/contract-repo";
|
|
13
|
+
import { answerPoint, askPoint, type PointsWriteDocument } from "./decide";
|
|
14
|
+
import pointAnswerSchema from "./point-answer.schema.json";
|
|
15
|
+
import { workspacePoints } from "./points-cli";
|
|
16
|
+
import pointsSchema from "./points.schema.json";
|
|
17
|
+
import pointsWriteSchema from "./points-write.schema.json";
|
|
18
|
+
import type { ModelAsk, WireAnswer } from "./points";
|
|
19
|
+
import { queryRecords } from "./records-cli";
|
|
20
|
+
|
|
21
|
+
const REF = join(REPO, "reference-workspace");
|
|
22
|
+
const POINTS = JSON.parse(readFileSync(join(REF, "decisions", "points.json"), "utf-8")) as { points: Record<string, unknown> };
|
|
23
|
+
|
|
24
|
+
const points = {
|
|
25
|
+
points: {
|
|
26
|
+
...POINTS.points,
|
|
27
|
+
"needs-decision": {
|
|
28
|
+
title: "Does this change need a decision",
|
|
29
|
+
question: { type: "noul", instructions: "Does the change make a choice nobody has recorded?", criteria: { true: "It needs a decision.", false: "It does not." } },
|
|
30
|
+
inputs: { commit: "the commit, as graph --intent lists it" },
|
|
31
|
+
deciders: [
|
|
32
|
+
{ kind: "model", backend: "systemone", model: "jev-1.13.0", threshold: 0.8 },
|
|
33
|
+
{ kind: "quorum", count: 2, roles: ["lead"] },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const write = contract(pointsWriteSchema);
|
|
40
|
+
const read = contract(pointsSchema);
|
|
41
|
+
|
|
42
|
+
let root: string;
|
|
43
|
+
const on = "2026-09-25";
|
|
44
|
+
|
|
45
|
+
/** A stub model decider answering every point with `answer`, at the pinned model. */
|
|
46
|
+
const stub =
|
|
47
|
+
(answer: WireAnswer): ModelAsk =>
|
|
48
|
+
async (req) => ({ model: req.model, answer });
|
|
49
|
+
|
|
50
|
+
const big = { "work-item.criteria": 9, "work-item.fits_small": false, "work-item.fits_medium": false };
|
|
51
|
+
const confident: WireAnswer = { type: "choice", choice: "large", probabilities: { small: 0.01, medium: 0.07, large: 0.92 }, confidence: 0.88 };
|
|
52
|
+
const unsure: WireAnswer = { type: "choice", choice: "medium", probabilities: { small: 0.2, medium: 0.45, large: 0.35 }, confidence: 0.175 };
|
|
53
|
+
|
|
54
|
+
function ok(doc: PointsWriteDocument): Extract<PointsWriteDocument, { question: unknown }> {
|
|
55
|
+
write.expectValid(doc);
|
|
56
|
+
if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
|
|
57
|
+
return doc;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function refused(doc: PointsWriteDocument): string {
|
|
61
|
+
write.expectValid(doc);
|
|
62
|
+
if (!("error" in doc)) throw new Error("expected a refusal");
|
|
63
|
+
return doc.error.code;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const answers = () => readdirSync(join(root, "answers")).filter((f) => f.endsWith(".md")).sort();
|
|
67
|
+
const fm = (path: string) => readFileSync(join(root, path), "utf-8");
|
|
68
|
+
|
|
69
|
+
beforeAll(() => {
|
|
70
|
+
root = repo(
|
|
71
|
+
{
|
|
72
|
+
"chant.workspace.json": JSON.stringify(
|
|
73
|
+
{ name: "studio", schema: 1, members: [{ name: "app", dir: "app", kind: "other", because: "a plain Node server" }], records: [{ kind: "answers/answer.kind.mjs" }] },
|
|
74
|
+
null,
|
|
75
|
+
2,
|
|
76
|
+
),
|
|
77
|
+
"app/server.mjs": "export const port = 8080;\n",
|
|
78
|
+
"decisions/points.json": JSON.stringify(points, null, 2),
|
|
79
|
+
".chant/trust.json": JSON.stringify({ schema: 1, roles: { agent: ["bot"], lead: ["alice", "bob", "bot"] } }),
|
|
80
|
+
},
|
|
81
|
+
false,
|
|
82
|
+
);
|
|
83
|
+
cpSync(join(REF, "answers", "answer.kind.mjs"), join(root, "answers", "answer.kind.mjs"), { recursive: true });
|
|
84
|
+
cpSync(join(REF, "answers", "answer.schema.json"), join(root, "answers", "answer.schema.json"));
|
|
85
|
+
commitAll(root, "c0");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
afterAll(cleanScratch);
|
|
89
|
+
|
|
90
|
+
describe("points ask (#2739)", () => {
|
|
91
|
+
test("a table's answer is answered at once", async () => {
|
|
92
|
+
const doc = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: { "work-item.fits_small": true }, subject: "W-001", on }));
|
|
93
|
+
expect(doc.question).toMatchObject({ state: "answered", open: false, answer: "small", decider: { kind: "table", row: 0 }, subject: "W-001", answeredOn: on, model: null });
|
|
94
|
+
expect(doc.written).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("a model answer at or above its threshold is written as proposed, with its values and source", async () => {
|
|
98
|
+
const doc = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: big, subject: "W-002", ask: stub(confident), on }));
|
|
99
|
+
expect(doc.question).toMatchObject({
|
|
100
|
+
state: "proposed",
|
|
101
|
+
open: true,
|
|
102
|
+
answer: "large",
|
|
103
|
+
decider: { kind: "model", backend: "systemone", model: "bosun-v3.1-1.7b" },
|
|
104
|
+
probabilities: { small: 0.01, medium: 0.07, large: 0.92 },
|
|
105
|
+
confidence: 0.88,
|
|
106
|
+
threshold: 0.8,
|
|
107
|
+
model: { answer: "large", confidence: 0.88, threshold: 0.8, observed: true },
|
|
108
|
+
escalations: [{ kind: "table", reason: "no row matches these inputs" }],
|
|
109
|
+
});
|
|
110
|
+
expect(doc.id).toBe(`slice-tier-${doc.question.inputsHash.slice(0, 12)}`);
|
|
111
|
+
expect(fm(doc.path)).toContain('source:\n via: "cli"\n model: "bosun-v3.1-1.7b"');
|
|
112
|
+
const records = await queryRecords({ kind: "answers/answer.kind.mjs", cwd: root });
|
|
113
|
+
if ("error" in records) throw new Error(records.error.message);
|
|
114
|
+
expect(records.records.find((r) => r.id === doc.id)).toMatchObject({ valid: true, state: "proposed", warnings: [] });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("asking again with the same inputs returns the existing record", async () => {
|
|
118
|
+
const before = answers();
|
|
119
|
+
const first = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: big, subject: "W-002", ask: stub(unsure), on }));
|
|
120
|
+
// Key order does not matter: the inputs hash over canonical JSON.
|
|
121
|
+
const again = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: Object.fromEntries(Object.entries(big).reverse()), ask: stub(unsure) }));
|
|
122
|
+
for (const d of [first, again]) expect(d).toMatchObject({ reused: true, written: false, question: { state: "proposed", answer: "large" } });
|
|
123
|
+
expect(answers()).toEqual(before);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("below its threshold the question is escalated and listed as open for a quorum, with the model's answer", async () => {
|
|
127
|
+
const doc = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: { ...big, "work-item.criteria": 12 }, subject: "W-003", ask: stub(unsure), on }));
|
|
128
|
+
expect(doc.question).toMatchObject({ state: "escalated", open: true, answer: null, decider: { kind: "quorum", count: 1 } });
|
|
129
|
+
expect(fm(doc.path)).not.toMatch(/^answer:/m);
|
|
130
|
+
const listed = await workspacePoints({ cwd: root, open: true });
|
|
131
|
+
read.expectValid(listed);
|
|
132
|
+
if ("error" in listed) throw new Error(listed.error.message);
|
|
133
|
+
const q = listed.questions.find((x) => x.id === doc.id)!;
|
|
134
|
+
expect(q).toMatchObject({ state: "escalated", open: true, current: true, model: { answer: "medium", confidence: 0.175, threshold: 0.8, model: "bosun-v3.1-1.7b", observed: false } });
|
|
135
|
+
expect(q.escalations.map((e) => e.reason)).toEqual(["no row matches these inputs", "not observed: confidence 0.175 is below the threshold 0.8"]);
|
|
136
|
+
expect(listed.questions.every((x) => x.open)).toBe(true);
|
|
137
|
+
expect(listed.points.map((p) => p.name)).toEqual(["slice-tier", "ship-skip", "finding-triage", "needs-a-decision", "needs-decision"]);
|
|
138
|
+
expect(listed.points[0].inputs[0]).toEqual({ name: "work-item.criteria", output: "work-item", description: "acceptance criteria in the work item" });
|
|
139
|
+
|
|
140
|
+
// A later ask still escalating keeps the standing record; one a model answers rewrites it, as proposed.
|
|
141
|
+
const still = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: { ...big, "work-item.criteria": 12 } }));
|
|
142
|
+
expect(still).toMatchObject({ reused: true, question: { state: "escalated" } });
|
|
143
|
+
const now = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: { ...big, "work-item.criteria": 12 }, ask: stub(confident), on }));
|
|
144
|
+
expect(now).toMatchObject({ id: doc.id, path: doc.path, reused: false, question: { state: "proposed", subject: "W-003", answer: "large" } });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("with --dry-run nothing is written, and the text is returned", async () => {
|
|
148
|
+
const before = answers();
|
|
149
|
+
const doc = ok(await askPoint({ cwd: root, point: "ship-skip", inputs: { "release.units": 3 }, dryRun: true, on }));
|
|
150
|
+
expect(doc).toMatchObject({ written: false, dryRun: true, question: { state: "answered", answer: false } });
|
|
151
|
+
expect((doc as { text?: string }).text).toContain("answer: false");
|
|
152
|
+
expect(answers()).toEqual(before);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("refusals: an unknown point, undeclared inputs, a closed-failing backend", async () => {
|
|
156
|
+
expect(refused(await askPoint({ cwd: root, point: "nope", inputs: {} }))).toBe("point-unknown");
|
|
157
|
+
expect(refused(await askPoint({ cwd: root, point: "slice-tier", inputs: { "contract.size": 3 } }))).toBe("point-inputs-invalid");
|
|
158
|
+
expect(refused(await askPoint({ cwd: root, point: "slice-tier", inputs: [] }))).toBe("point-inputs-invalid");
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("points answer (#2739)", () => {
|
|
163
|
+
test("a person confirms a model's proposal, which keeps the model as its decider", async () => {
|
|
164
|
+
const asked = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: big, ask: stub(confident) }));
|
|
165
|
+
const doc = ok(await answerPoint({ cwd: root, id: asked.id, answer: "large", by: ["alice"], on }));
|
|
166
|
+
expect(doc.question).toMatchObject({ state: "answered", open: false, answer: "large", decider: { kind: "model", model: "bosun-v3.1-1.7b" }, confidence: 0.88, answeredBy: ["alice"], answeredOn: on });
|
|
167
|
+
expect(refused(await answerPoint({ cwd: root, id: asked.id, answer: "small", by: ["alice"] }))).toBe("record-closed");
|
|
168
|
+
// Asking again returns the confirmed answer.
|
|
169
|
+
expect(ok(await askPoint({ cwd: root, point: "slice-tier", inputs: big, ask: stub(unsure) }))).toMatchObject({ reused: true, question: { state: "answered" } });
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("people answering otherwise is the quorum's answer, and the proposal moves into the escalations", async () => {
|
|
173
|
+
const asked = ok(await askPoint({ cwd: root, point: "slice-tier", inputs: { ...big, "work-item.criteria": 4 }, ask: stub(confident), on }));
|
|
174
|
+
expect(refused(await answerPoint({ cwd: root, id: asked.id, answer: "tiny", by: ["alice"] }))).toBe("answer-not-candidate");
|
|
175
|
+
const doc = ok(await answerPoint({ cwd: root, id: asked.id, answer: "medium", by: ["bob"], on }));
|
|
176
|
+
expect(doc.question).toMatchObject({ state: "answered", answer: "medium", decider: { kind: "quorum", count: 1, by: ["bob"] }, probabilities: null, confidence: null });
|
|
177
|
+
expect(doc.question.escalations.at(-1)).toMatchObject({ kind: "model", answer: "large", confidence: 0.88, reason: "proposed large, and people answered medium" });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("the quorum counts distinct people holding its roles, never an agent", async () => {
|
|
181
|
+
const asked = ok(await askPoint({ cwd: root, point: "needs-decision", inputs: { commit: { sha: "abc" } }, ask: stub({ type: "noul", noul: 0.55 }), on }));
|
|
182
|
+
expect(asked.question).toMatchObject({ state: "escalated", model: { answer: true, confidence: 0.55, observed: false } });
|
|
183
|
+
expect(refused(await answerPoint({ cwd: root, id: asked.id, answer: "yes", by: ["alice", "Alice ", "bot", "carol"] }))).toBe("quorum-not-met");
|
|
184
|
+
const doc = ok(await answerPoint({ cwd: root, id: asked.id, answer: "yes", by: ["alice", "bob"], on }));
|
|
185
|
+
expect(doc.question).toMatchObject({ state: "answered", answer: true, decider: { kind: "quorum", count: 2, roles: ["lead"], by: ["alice", "bob"] } });
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("an unknown id is refused", async () => {
|
|
189
|
+
expect(refused(await answerPoint({ cwd: root, id: "slice-tier-000000000000", answer: "small", by: ["alice"] }))).toBe("record-not-found");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("every record written follows point-answer.schema.json, and the reference workspace carries a copy of it", () => {
|
|
193
|
+
expect(JSON.parse(readFileSync(join(REF, "answers", "answer.schema.json"), "utf-8"))).toEqual(pointAnswerSchema);
|
|
194
|
+
expect(answers().length).toBeGreaterThan(3);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("the points file in check and on read (#2738)", () => {
|
|
199
|
+
test("a changed point warns on its answers, and an invalid points file is WSP116 and a points-invalid source", async () => {
|
|
200
|
+
const file = join(root, "decisions", "points.json");
|
|
201
|
+
const changed = JSON.parse(readFileSync(file, "utf-8"));
|
|
202
|
+
changed.points["slice-tier"].title = "Which builder builds this work item";
|
|
203
|
+
writeFileSync(file, JSON.stringify(changed, null, 2));
|
|
204
|
+
const doc = await workspacePoints({ cwd: root });
|
|
205
|
+
read.expectValid(doc);
|
|
206
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
207
|
+
const slice = doc.questions.filter((q) => q.point === "slice-tier");
|
|
208
|
+
expect(slice.every((q) => q.current === false && q.warnings.some((w) => w.code === "answer-point-changed"))).toBe(true);
|
|
209
|
+
expect((await runDeclarationChecks(root)).diagnostics.filter((d) => d.ruleId === "WSP116")).toEqual([]);
|
|
210
|
+
|
|
211
|
+
changed.points["slice-tier"].deciders.pop();
|
|
212
|
+
writeFileSync(file, JSON.stringify(changed, null, 2));
|
|
213
|
+
const report = await runDeclarationChecks(root);
|
|
214
|
+
expect(report.diagnostics.filter((d) => d.ruleId === "WSP116").map((d) => d.message)).toEqual([
|
|
215
|
+
"the workspace declares the answer kind answers/answer.kind.mjs, whose points file decisions/points.json has points.slice-tier.deciders, which must end in a quorum: when no table row or model answers, people do",
|
|
216
|
+
]);
|
|
217
|
+
expect(report.ok).toBe(false);
|
|
218
|
+
const broken = await workspacePoints({ cwd: root });
|
|
219
|
+
read.expectValid(broken);
|
|
220
|
+
if ("error" in broken) throw new Error(broken.error.message);
|
|
221
|
+
expect(broken.sources[0].reason?.code).toBe("points-invalid");
|
|
222
|
+
expect(refused(await askPoint({ cwd: root, point: "slice-tier", inputs: big }))).toBe("points-invalid");
|
|
223
|
+
});
|
|
224
|
+
});
|