@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
|
@@ -629,9 +629,15 @@ export interface WriteChannel {
|
|
|
629
629
|
opensInitial?: boolean;
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
-
/** What a channel's write adds to the command line's: who the author
|
|
632
|
+
/** What a channel's write adds to the command line's: who named the author, and how it came. */
|
|
633
633
|
export interface ChannelOptions {
|
|
634
|
-
/**
|
|
634
|
+
/**
|
|
635
|
+
* Who the caller names as the record's author (#2756). Written to the
|
|
636
|
+
* kind's `proposedBy` field when the kind declares one and the record
|
|
637
|
+
* opens (or stays) in its first state — a proposal naming its proposer.
|
|
638
|
+
* Otherwise written to `reviews.decider`, as before: a record opening (or
|
|
639
|
+
* moving) straight to another state names who decided it.
|
|
640
|
+
*/
|
|
635
641
|
by?: string;
|
|
636
642
|
through?: WriteChannel;
|
|
637
643
|
}
|
|
@@ -640,18 +646,8 @@ export interface ChannelOptions {
|
|
|
640
646
|
function applyChannel(o: Opened, fields: Record<string, unknown>, opts: ChannelOptions, isNew: boolean, flag: string): Record<string, unknown> {
|
|
641
647
|
const { kind } = o.loaded;
|
|
642
648
|
const out = { ...fields };
|
|
643
|
-
if (opts.by !== undefined) {
|
|
644
|
-
if (!kind.reviews) throw new RecordWriteError("write-usage-invalid", `by names a record's author, the kind's reviews.decider field, and the ${kind.name} kind declares no reviews`);
|
|
645
|
-
if (opts.by.trim() === "") throw new RecordWriteError("write-usage-invalid", "by needs the name of the person or agent that decided");
|
|
646
|
-
const f = kind.reviews.decider;
|
|
647
|
-
if (out[f] !== undefined && out[f] !== null && out[f] !== opts.by) {
|
|
648
|
-
throw new RecordWriteError("write-input-invalid", `the fields given with ${flag} set ${f} to ${JSON.stringify(out[f])}, and by names ${JSON.stringify(opts.by)}: give one author`);
|
|
649
|
-
}
|
|
650
|
-
out[f] = opts.by;
|
|
651
|
-
}
|
|
652
649
|
const through = opts.through;
|
|
653
|
-
if (
|
|
654
|
-
if (isNew && through.opensInitial && kind.states && kind.stateField !== undefined) {
|
|
650
|
+
if (isNew && through?.opensInitial && kind.states && kind.stateField !== undefined) {
|
|
655
651
|
const first = kind.states[0];
|
|
656
652
|
const state = out[kind.stateField];
|
|
657
653
|
if (state === undefined) out[kind.stateField] = first;
|
|
@@ -662,6 +658,27 @@ function applyChannel(o: Opened, fields: Record<string, unknown>, opts: ChannelO
|
|
|
662
658
|
);
|
|
663
659
|
}
|
|
664
660
|
}
|
|
661
|
+
if (opts.by !== undefined) {
|
|
662
|
+
if (opts.by.trim() === "") throw new RecordWriteError("write-usage-invalid", "by needs the name of the person or agent that proposed or decided");
|
|
663
|
+
// A new record that opens in the kind's first state is a proposal, when the kind opts in with proposedBy (#2756):
|
|
664
|
+
// by names the proposer, kept apart from reviews.decider, which stays null until the record is decided.
|
|
665
|
+
const opensInitial = isNew && !!kind.states && kind.stateField !== undefined && (out[kind.stateField] === undefined || out[kind.stateField] === kind.states[0]);
|
|
666
|
+
if (kind.proposedBy && opensInitial) {
|
|
667
|
+
const f = kind.proposedBy.field;
|
|
668
|
+
if (out[f] !== undefined && out[f] !== null && out[f] !== opts.by) {
|
|
669
|
+
throw new RecordWriteError("write-input-invalid", `the fields given with ${flag} set ${f} to ${JSON.stringify(out[f])}, and by names ${JSON.stringify(opts.by)}: give one proposer`);
|
|
670
|
+
}
|
|
671
|
+
out[f] = opts.by;
|
|
672
|
+
} else {
|
|
673
|
+
if (!kind.reviews) throw new RecordWriteError("write-usage-invalid", `by names a record's author, the kind's reviews.decider field, and the ${kind.name} kind declares no reviews`);
|
|
674
|
+
const f = kind.reviews.decider;
|
|
675
|
+
if (out[f] !== undefined && out[f] !== null && out[f] !== opts.by) {
|
|
676
|
+
throw new RecordWriteError("write-input-invalid", `the fields given with ${flag} set ${f} to ${JSON.stringify(out[f])}, and by names ${JSON.stringify(opts.by)}: give one author`);
|
|
677
|
+
}
|
|
678
|
+
out[f] = opts.by;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (!through) return out;
|
|
665
682
|
if (kind.source && (isNew || kind.source.field in out)) {
|
|
666
683
|
const f = kind.source.field;
|
|
667
684
|
const block = out[f];
|
|
@@ -984,7 +1001,7 @@ async function seal(sign: string | true, cwd: string, v: { record: string; diges
|
|
|
984
1001
|
// ── The command ──────────────────────────────────────────────────────────────
|
|
985
1002
|
|
|
986
1003
|
export const WRITE_USAGE = [
|
|
987
|
-
"chant workspace records new [<kind file or declared kind>] --from <file|-> [--prefix <prefix>] [--sign [<key file>]] [--dry-run]",
|
|
1004
|
+
"chant workspace records new [<kind file or declared kind>] --from <file|-> [--prefix <prefix>] [--by <name>] [--sign [<key file>]] [--dry-run]",
|
|
988
1005
|
"chant workspace records amend <id> [--kind <kind file>] --set <file|-> [--sign [<key file>]] [--dry-run]",
|
|
989
1006
|
"chant workspace records review <id> [--kind <kind file>] --verdict agree|dissent|abstain --by <principal> [--note <text>] [--session <id>] [--sign [<key file>]] [--dry-run]",
|
|
990
1007
|
"chant workspace records close <session id> [--kind <session kind file>] [--dry-run]",
|
|
@@ -1087,7 +1104,7 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
|
|
|
1087
1104
|
if (typeof kind !== "string") return print(kind);
|
|
1088
1105
|
const input = readInput(RECORDS_NEW_SCHEMA_ID, "--from", args.migrateFrom, cwd);
|
|
1089
1106
|
if (typeof input !== "string") return print(input);
|
|
1090
|
-
return print(await newRecord({ kind, fields: input, prefix: args.prefix, sign: args.sign, dryRun: args.dryRun, cwd }));
|
|
1107
|
+
return print(await newRecord({ kind, fields: input, prefix: args.prefix, by: args.by, sign: args.sign, dryRun: args.dryRun, cwd }));
|
|
1091
1108
|
}
|
|
1092
1109
|
const schema = verb === "amend" ? RECORDS_AMEND_SCHEMA_ID : RECORDS_REVIEW_SCHEMA_ID;
|
|
1093
1110
|
const id = args.extraPositional2;
|
|
@@ -1095,6 +1112,7 @@ export async function runRecordsWrite(ctx: CommandContext): Promise<number> {
|
|
|
1095
1112
|
const kind = args.kind !== undefined ? resolveWriteKind(args.kind, cwd) : declaredWriteKind(schema, cwd, "--kind <kind file> is required");
|
|
1096
1113
|
if (typeof kind !== "string") return print(kind);
|
|
1097
1114
|
if (verb === "amend") {
|
|
1115
|
+
if (args.by !== undefined) return print(usage(schema, "--by is not taken by amend: set decided_by, or a kind's proposedBy field, with --set"));
|
|
1098
1116
|
const input = readInput(schema, "--set", args.set, cwd);
|
|
1099
1117
|
if (typeof input !== "string") return print(input);
|
|
1100
1118
|
return print(await amendRecord({ kind, id, fields: input, sign: args.sign, dryRun: args.dryRun, cwd }));
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
},
|
|
117
117
|
"record": {
|
|
118
118
|
"type": "object",
|
|
119
|
-
"required": ["id", "path", "state", "valid", "reasons", "supersededBy", "data"],
|
|
119
|
+
"required": ["id", "path", "state", "valid", "reasons", "supersededBy", "remediatedBy", "data"],
|
|
120
120
|
"properties": {
|
|
121
121
|
"id": { "type": ["string", "null"], "description": "Null when the record could not be parsed or has no string id. For a content-addressed kind (idFrom sha256, added by #2664), the lowercase hex SHA-256 of the file's bytes, the same string a pin of the file carries." },
|
|
122
122
|
"path": { "type": "string", "description": "From the repository root, with / separators." },
|
|
@@ -127,6 +127,11 @@
|
|
|
127
127
|
"type": ["string", "null"],
|
|
128
128
|
"description": "The id of the closed record whose supersedes link replaces this one. Derived from that record's link, never from this record's own fields."
|
|
129
129
|
},
|
|
130
|
+
"remediatedBy": {
|
|
131
|
+
"description": "Added in contract 1 by #2774. The ids of records whose remediates link names this one, in path order. Unlike supersededBy, several records may remediate the same one, and a remediation never changes this record's state or supersededBy: it stays exactly as current as it was.",
|
|
132
|
+
"type": "array",
|
|
133
|
+
"items": { "type": "string" }
|
|
134
|
+
},
|
|
130
135
|
"data": {
|
|
131
136
|
"type": ["object", "null"],
|
|
132
137
|
"description": "The record's structured core as JSON: the front matter, or for a kind of format json the whole file (#2664). It follows the kind's schema when valid is true. Null when it could not be parsed."
|
|
@@ -138,7 +143,7 @@
|
|
|
138
143
|
"items": { "$ref": "#/$defs/asset" }
|
|
139
144
|
},
|
|
140
145
|
"warnings": {
|
|
141
|
-
"description": "Added in contract 1 by #2549. Findings that leave the record valid: a pinned file that changed, went missing or did not follow a superseding decision, a supersedes link that has no effect yet, (added by #2654) an evidence list that is empty, (added by #2672) a verdict that names no digest, or (added by #2683) a work record's links and dates, or (added by #2688) a record whose author seal is not attested under a signers file at base. A work record never carries record-no-evidence; a done one with no evidence carries work-done-unpinned. valid and --current do not look at them.",
|
|
146
|
+
"description": "Added in contract 1 by #2549. Findings that leave the record valid: a pinned file that changed, went missing or did not follow a superseding decision, a supersedes link that has no effect yet, (added by #2654) an evidence list that is empty, (added by #2672) a verdict that names no digest, or (added by #2683) a work record's links and dates, or (added by #2688) a record whose author seal is not attested under a signers file at base, or (added by #2772) a work record's acceptance criteria unmet when it is done, or met by a manual verdict from its implementer. A work record never carries record-no-evidence; a done one with no evidence carries work-done-unpinned. valid and --current do not look at them.",
|
|
142
147
|
"type": "array",
|
|
143
148
|
"items": { "$ref": "#/$defs/warning" }
|
|
144
149
|
},
|
|
@@ -177,6 +182,14 @@
|
|
|
177
182
|
"description": "Added in contract 1 by #2683, on every parsed record of a work kind. Each decision the record implements, with its state now; null for an id no decision has.",
|
|
178
183
|
"type": "array",
|
|
179
184
|
"items": { "$ref": "#/$defs/workLink" }
|
|
185
|
+
},
|
|
186
|
+
"acceptance": {
|
|
187
|
+
"description": "Added in contract 1 by #2772, on every parsed record of a work kind whose work block names acceptance criteria. How many of the record's criteria passing evidence meets: evidence naming the criterion, with result pass and the criterion's verification, and for a manual criterion a by other than the implementer. Null when the record lists no criteria, so a record without them reads as before.",
|
|
188
|
+
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/workAcceptance" }]
|
|
189
|
+
},
|
|
190
|
+
"lease": {
|
|
191
|
+
"description": "Added in contract 1 by #2732, on every record of a work kind with an id, read in the working tree (absent under --at). The item's active work lease (chant workspace work claim), or null when nobody holds one live. Read from the local lease refs, refs/chant/lease/[_members/<member>/]work/<id> and the remote's as last fetched, of the ledger owning the work kind file; records never fetches.",
|
|
192
|
+
"oneOf": [{ "type": "null" }, { "$ref": "#/$defs/workLease" }]
|
|
180
193
|
}
|
|
181
194
|
},
|
|
182
195
|
"allOf": [
|
|
@@ -187,6 +200,37 @@
|
|
|
187
200
|
}
|
|
188
201
|
]
|
|
189
202
|
},
|
|
203
|
+
"workAcceptance": {
|
|
204
|
+
"type": "object",
|
|
205
|
+
"required": ["met", "total", "criteria"],
|
|
206
|
+
"properties": {
|
|
207
|
+
"met": { "type": "integer", "minimum": 0, "description": "How many criteria passing evidence meets." },
|
|
208
|
+
"total": { "type": "integer", "minimum": 0, "description": "How many criteria the record lists." },
|
|
209
|
+
"criteria": {
|
|
210
|
+
"type": "array",
|
|
211
|
+
"description": "Each criterion, in the record's order.",
|
|
212
|
+
"items": {
|
|
213
|
+
"type": "object",
|
|
214
|
+
"required": ["id", "verification", "met"],
|
|
215
|
+
"properties": {
|
|
216
|
+
"id": { "type": "string" },
|
|
217
|
+
"verification": { "enum": ["unit", "integration", "e2e", "runtime", "manual"], "description": "The verification the criterion expects." },
|
|
218
|
+
"met": { "type": "boolean" }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
"workLease": {
|
|
225
|
+
"type": "object",
|
|
226
|
+
"required": ["holder", "token", "acquiredAt", "expiresAt"],
|
|
227
|
+
"properties": {
|
|
228
|
+
"holder": { "type": "string", "description": "Who holds the lease, as --holder gave it." },
|
|
229
|
+
"token": { "type": "string", "description": "The fencing token: new on every claim, kept by every renew." },
|
|
230
|
+
"acquiredAt": { "type": "string", "description": "When the lease was claimed, ISO 8601." },
|
|
231
|
+
"expiresAt": { "type": "string", "description": "When the lease expires unless renewed, ISO 8601." }
|
|
232
|
+
}
|
|
233
|
+
},
|
|
190
234
|
"asset": {
|
|
191
235
|
"type": "object",
|
|
192
236
|
"required": ["path", "sha256", "actual", "state"],
|
|
@@ -217,6 +261,11 @@
|
|
|
217
261
|
"work-done-unpinned",
|
|
218
262
|
"work-closed-without-date",
|
|
219
263
|
"work-done-gap-open",
|
|
264
|
+
"work-acceptance-unmet",
|
|
265
|
+
"work-acceptance-self-verified",
|
|
266
|
+
"answer-points-unreadable",
|
|
267
|
+
"answer-point-unknown",
|
|
268
|
+
"answer-point-changed",
|
|
220
269
|
"record-unattested"
|
|
221
270
|
]
|
|
222
271
|
},
|
|
@@ -355,6 +404,8 @@
|
|
|
355
404
|
"record-id-duplicate",
|
|
356
405
|
"record-supersedes-unknown",
|
|
357
406
|
"record-supersedes-conflict",
|
|
407
|
+
"record-remediates-unknown",
|
|
408
|
+
"record-remediates-not-closed",
|
|
358
409
|
"session-seal-mismatch",
|
|
359
410
|
"session-verdict-unknown-record"
|
|
360
411
|
]
|
|
@@ -11,12 +11,17 @@ const REPO = join(import.meta.dirname, "..", "..", "..", "..");
|
|
|
11
11
|
const DECISIONS = join(REPO, "docs", "design", "decisions");
|
|
12
12
|
const SAMPLE = readFileSync(join(DECISIONS, "ws-003-seal-scope.md"), "utf-8");
|
|
13
13
|
|
|
14
|
-
/** A copy of a real decision with its id, state and
|
|
15
|
-
function decision(id: string, state = "decided", supersedes: string[] = []): string {
|
|
14
|
+
/** A copy of a real decision with its id, state, supersedes and remediates links replaced. */
|
|
15
|
+
function decision(id: string, state = "decided", supersedes: string[] = [], remediates: string[] = []): string {
|
|
16
16
|
const links = supersedes.length === 0 ? "supersedes: []" : `supersedes:\n${supersedes.map((d) => ` - decision: "${d}"`).join("\n")}`;
|
|
17
|
-
|
|
17
|
+
let text = SAMPLE.replace(/^id: .*$/m, `id: "${id}"`)
|
|
18
18
|
.replace(/^state: .*$/m, `state: "${state}"`)
|
|
19
19
|
.replace(/^supersedes:(?: \[\])?\n(?: .*\n)*/m, `${links}\n`);
|
|
20
|
+
if (remediates.length > 0) {
|
|
21
|
+
const block = `remediates:\n${remediates.map((d) => ` - decision: "${d}"`).join("\n")}\n`;
|
|
22
|
+
text = text.replace(/^evidence:/m, `${block}evidence:`);
|
|
23
|
+
}
|
|
24
|
+
return text;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
let dir: string;
|
|
@@ -235,6 +240,46 @@ describe("readRecords", () => {
|
|
|
235
240
|
expect(codes(records[2])).toEqual(["record-supersedes-conflict"]);
|
|
236
241
|
});
|
|
237
242
|
|
|
243
|
+
test("a remediates link to a missing id is record-remediates-unknown", async () => {
|
|
244
|
+
write("ws-002-b.md", decision("ws-002", "decided", [], ["ws-404"]));
|
|
245
|
+
const [r] = (await read()).records;
|
|
246
|
+
expect(codes(r)).toEqual(["record-remediates-unknown"]);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("a remediates link to an open record is record-remediates-not-closed: a record still open is amended instead", async () => {
|
|
250
|
+
write("ws-001-a.md", decision("ws-001", "decided"));
|
|
251
|
+
write("ws-002-b.md", decision("ws-002", "ratified", [], ["ws-001"]));
|
|
252
|
+
const [ws001, ws002] = (await read()).records;
|
|
253
|
+
expect(codes(ws002)).toEqual(["record-remediates-not-closed"]);
|
|
254
|
+
expect(ws001.remediatedBy).toEqual([]);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("remediation leaves the target current and its state unchanged, and several records may remediate the same one (#2774)", async () => {
|
|
258
|
+
write("ws-001-a.md", decision("ws-001", "ratified"));
|
|
259
|
+
write("ws-002-b.md", decision("ws-002", "ratified", [], ["ws-001"]));
|
|
260
|
+
write("ws-003-c.md", decision("ws-003", "ratified", [], ["ws-001"]));
|
|
261
|
+
const all = await read();
|
|
262
|
+
const [ws001] = all.records;
|
|
263
|
+
expect(ws001.state).toBe("ratified");
|
|
264
|
+
expect(ws001.supersededBy).toBeNull();
|
|
265
|
+
expect(ws001.remediatedBy).toEqual(["ws-002", "ws-003"]);
|
|
266
|
+
expect(all.summary.superseded).toBe(0);
|
|
267
|
+
const current = await read({ current: true });
|
|
268
|
+
expect(current.records.map((r) => r.id)).toEqual(["ws-001", "ws-002", "ws-003"]);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("remediates and supersedes are independent links, and a record may carry both to different targets (#2774)", async () => {
|
|
272
|
+
write("ws-001-a.md", decision("ws-001", "ratified"));
|
|
273
|
+
write("ws-002-b.md", decision("ws-002", "ratified"));
|
|
274
|
+
write("ws-003-c.md", decision("ws-003", "ratified", ["ws-002"], ["ws-001"]));
|
|
275
|
+
const all = await read();
|
|
276
|
+
expect(all.records.map((r) => [r.id, r.supersededBy, r.remediatedBy])).toEqual([
|
|
277
|
+
["ws-001", null, ["ws-003"]],
|
|
278
|
+
["ws-002", "ws-003", []],
|
|
279
|
+
["ws-003", null, []],
|
|
280
|
+
]);
|
|
281
|
+
});
|
|
282
|
+
|
|
238
283
|
test("a missing records directory is location-missing", async () => {
|
|
239
284
|
const loaded = await loadRecordKind(join(dir, "decisions", "decision.kind.mjs"));
|
|
240
285
|
const moved: LoadedRecordKind = { ...loaded, dir: join(dir, "nowhere") };
|
package/src/workspace/records.ts
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* record a kind locates, parses its structured core (the front matter, as the
|
|
7
7
|
* JSON subset of YAML, or the whole file as one I-JSON object, ws-053),
|
|
8
8
|
* validates it against the kind's schema and derives supersession from the
|
|
9
|
-
* records' own `supersedes` links
|
|
10
|
-
*
|
|
9
|
+
* records' own `supersedes` links, and remediation from their `remediates`
|
|
10
|
+
* links (#2774): a later record fixing a closed one's consequence without
|
|
11
|
+
* replacing it, which leaves the closed record's status unchanged. It never
|
|
12
|
+
* writes a record; `records-write.ts` does, through the rules here (#2670).
|
|
11
13
|
*
|
|
12
14
|
* A record that fails any of that is still returned, with reason codes, and the
|
|
13
15
|
* read succeeds. Only a failure to read the kind, its schema or the revision is
|
|
@@ -31,7 +33,8 @@ import { joinSessions, type SessionCitation } from "./record-sessions";
|
|
|
31
33
|
import type { RecordSource } from "./record-source";
|
|
32
34
|
import { sourceBlock, sourceBlockProblems, transcriptDrift } from "./source-block";
|
|
33
35
|
import type { WorkspaceTree } from "./tree";
|
|
34
|
-
import type { DecisionWork, WorkLink, WorkWarningCode } from "./work";
|
|
36
|
+
import type { DecisionWork, WorkAcceptance, WorkLink, WorkWarningCode } from "./work";
|
|
37
|
+
import type { AnswerWarningCode } from "./points";
|
|
35
38
|
|
|
36
39
|
// ── Reason codes ─────────────────────────────────────────────────────────────
|
|
37
40
|
|
|
@@ -50,6 +53,10 @@ export const RECORD_REASON_CODES = [
|
|
|
50
53
|
"record-supersedes-unknown",
|
|
51
54
|
/** A second closed record supersedes a record another one already superseded. */
|
|
52
55
|
"record-supersedes-conflict",
|
|
56
|
+
/** A `remediates` link names an id no record has (#2774). */
|
|
57
|
+
"record-remediates-unknown",
|
|
58
|
+
/** A `remediates` link names a record that isn't closed: a record still open is amended instead (#2774). */
|
|
59
|
+
"record-remediates-not-closed",
|
|
53
60
|
/** A closed session's seal is not the digest of its text: it changed after it closed (#2673). */
|
|
54
61
|
"session-seal-mismatch",
|
|
55
62
|
/** A session's verdict names a record the kind's subject records do not have (#2673). */
|
|
@@ -175,8 +182,8 @@ export interface VerdictAttestation {
|
|
|
175
182
|
}
|
|
176
183
|
|
|
177
184
|
export interface RecordWarning {
|
|
178
|
-
/** A work kind's records also carry the codes of `WORK_WARNING_CODES` (#2683), and `records` adds `SEAL_WARNING_CODES` (#2688). */
|
|
179
|
-
code: RecordWarningCode | WorkWarningCode | SealWarningCode;
|
|
185
|
+
/** A work kind's records also carry the codes of `WORK_WARNING_CODES` (#2683), an answer kind's those of `ANSWER_WARNING_CODES` (#2739), and `records` adds `SEAL_WARNING_CODES` (#2688). */
|
|
186
|
+
code: RecordWarningCode | WorkWarningCode | SealWarningCode | AnswerWarningCode;
|
|
180
187
|
message: string;
|
|
181
188
|
}
|
|
182
189
|
|
|
@@ -278,6 +285,16 @@ export const recordKindSchema = z
|
|
|
278
285
|
* (ws-053). Optional, and only on a kind with states.
|
|
279
286
|
*/
|
|
280
287
|
supersedes: z.object({ field: z.string().min(1), key: z.string().min(1).optional() }).strict().optional(),
|
|
288
|
+
/**
|
|
289
|
+
* The field of links to records this one remediates: a later fix to a
|
|
290
|
+
* closed record's consequence, that leaves the closed record's status
|
|
291
|
+
* unchanged (#2774). Shaped like `supersedes`: with `key`, a list of
|
|
292
|
+
* objects whose `key` holds the target id; without it, one id or a list
|
|
293
|
+
* of ids. Optional, and only on a kind with states. A link is valid only
|
|
294
|
+
* when the target exists and is in one of `closedStates`; a target still
|
|
295
|
+
* open is amended instead, never remediated.
|
|
296
|
+
*/
|
|
297
|
+
remediates: z.object({ field: z.string().min(1), key: z.string().min(1).optional() }).strict().optional(),
|
|
281
298
|
/**
|
|
282
299
|
* How strongly each state is approved (#2524 D4). With it, a supersedes
|
|
283
300
|
* link takes effect when the new record's rank is above 0 and at least the
|
|
@@ -297,6 +314,15 @@ export const recordKindSchema = z
|
|
|
297
314
|
* (#2549). Optional.
|
|
298
315
|
*/
|
|
299
316
|
constrains: z.object({ field: z.string().min(1) }).strict().optional(),
|
|
317
|
+
/**
|
|
318
|
+
* The front-matter list of workspace paths a record puts out of scope
|
|
319
|
+
* (#2773): a file or a directory, from the workspace root. `chant
|
|
320
|
+
* workspace check --changes` reports a change to one as
|
|
321
|
+
* `change-out-of-scope` when the record is in hand for the change, and
|
|
322
|
+
* the record does not cover it even where its constrains would.
|
|
323
|
+
* Optional.
|
|
324
|
+
*/
|
|
325
|
+
outOfScope: z.object({ field: z.string().min(1) }).strict().optional(),
|
|
300
326
|
/**
|
|
301
327
|
* The front-matter list of review verdicts and the field naming the
|
|
302
328
|
* decider (#2671, #2672). With it, each record gets a digest of its text
|
|
@@ -306,6 +332,17 @@ export const recordKindSchema = z
|
|
|
306
332
|
* `withdrawn_on`. Optional.
|
|
307
333
|
*/
|
|
308
334
|
reviews: z.object({ field: z.string().min(1), decider: z.string().min(1) }).strict().optional(),
|
|
335
|
+
/**
|
|
336
|
+
* The front-matter field a new record's proposer is named on, when the
|
|
337
|
+
* kind opts in (#2756). `records new --by` and the MCP `records-new`
|
|
338
|
+
* tool's `by` write it for a record that opens in the kind's first
|
|
339
|
+
* state — a proposal naming who proposed it — instead of
|
|
340
|
+
* `reviews.decider`'s field, which `by` fills only when a new record
|
|
341
|
+
* opens straight into another state. Requires `states`. Optional: a kind
|
|
342
|
+
* without it keeps `by` writing `reviews.decider` for every new record,
|
|
343
|
+
* as before #2756.
|
|
344
|
+
*/
|
|
345
|
+
proposedBy: z.object({ field: z.string().min(1) }).strict().optional(),
|
|
309
346
|
/**
|
|
310
347
|
* The front-matter object that says where a record came from, opted in
|
|
311
348
|
* to the stored source block (#2708): `via`, `client`, `harness`,
|
|
@@ -348,6 +385,15 @@ export const recordKindSchema = z
|
|
|
348
385
|
* date. With it, `work.ts` gives each record `ready`, `blockedBy` and
|
|
349
386
|
* `implements`, and the read lists each decision with `implementedBy`.
|
|
350
387
|
* Optional.
|
|
388
|
+
*
|
|
389
|
+
* `acceptance` (#2772) names the front-matter list of a record's
|
|
390
|
+
* acceptance criteria, each an `id`, `text` and the `verification` it
|
|
391
|
+
* expects (`unit`, `integration`, `e2e`, `runtime` or `manual`), and the
|
|
392
|
+
* field naming the record's implementer, whose own `manual` verdict never
|
|
393
|
+
* counts. With it, each record gets `acceptance` (met and total), and a
|
|
394
|
+
* done record with a criterion that no passing evidence meets is warned
|
|
395
|
+
* `work-acceptance-unmet`, which `check` fails on (WSP117). Optional, and
|
|
396
|
+
* a record without the list is read as before.
|
|
351
397
|
*/
|
|
352
398
|
work: z
|
|
353
399
|
.object({
|
|
@@ -357,9 +403,21 @@ export const recordKindSchema = z
|
|
|
357
403
|
open: z.string().min(1),
|
|
358
404
|
done: z.string().min(1),
|
|
359
405
|
closedOn: z.string().min(1),
|
|
406
|
+
acceptance: z.object({ field: z.string().min(1), implementer: z.string().min(1) }).strict().optional(),
|
|
360
407
|
})
|
|
361
408
|
.strict()
|
|
362
409
|
.optional(),
|
|
410
|
+
/**
|
|
411
|
+
* An answer kind (ws-058, #2739): the decision points file its records
|
|
412
|
+
* answer, relative to this kind file. The records are written by `chant
|
|
413
|
+
* workspace points ask` and `points answer` in the shape of
|
|
414
|
+
* `point-answer.schema.json`, with an `id`, the states `escalated`,
|
|
415
|
+
* `proposed` and `answered`, and `answered` closed. With it, `records`
|
|
416
|
+
* warns about an answer whose point is gone or changed, `check` validates
|
|
417
|
+
* the points file (WSP116), and `points` lists the open questions.
|
|
418
|
+
* Optional.
|
|
419
|
+
*/
|
|
420
|
+
answers: z.object({ points: z.string().min(1) }).strict().optional(),
|
|
363
421
|
})
|
|
364
422
|
.strict()
|
|
365
423
|
.refine((k) => (k.idField === undefined) !== (k.idFrom === undefined), {
|
|
@@ -374,6 +432,10 @@ export const recordKindSchema = z
|
|
|
374
432
|
message: "a kind without states cannot have supersedes: a link takes effect only from a closed or ranked state",
|
|
375
433
|
path: ["supersedes"],
|
|
376
434
|
})
|
|
435
|
+
.refine((k) => k.states !== undefined || k.remediates === undefined, {
|
|
436
|
+
message: "a kind without states cannot have remediates: a link only ever targets a record in a closed state",
|
|
437
|
+
path: ["remediates"],
|
|
438
|
+
})
|
|
377
439
|
.refine((k) => k.states !== undefined || k.session === undefined, {
|
|
378
440
|
message: "a session kind must have states: a session is sealed when it reaches a closed state",
|
|
379
441
|
path: ["session"],
|
|
@@ -382,6 +444,10 @@ export const recordKindSchema = z
|
|
|
382
444
|
message: "a kind without states cannot have approval ranks",
|
|
383
445
|
path: ["approval"],
|
|
384
446
|
})
|
|
447
|
+
.refine((k) => k.states !== undefined || k.proposedBy === undefined, {
|
|
448
|
+
message: "a kind without states cannot have proposedBy: there is no first state for a record to open in",
|
|
449
|
+
path: ["proposedBy"],
|
|
450
|
+
})
|
|
385
451
|
.refine((k) => (k.closedStates ?? []).every((s) => (k.states ?? []).includes(s)), {
|
|
386
452
|
message: "every closed state must be listed in states",
|
|
387
453
|
path: ["closedStates"],
|
|
@@ -393,7 +459,27 @@ export const recordKindSchema = z
|
|
|
393
459
|
.refine((k) => !k.work || (k.states !== undefined && k.states.includes(k.work.open) && k.states.includes(k.work.done)), {
|
|
394
460
|
message: "a work kind must have states, and its work open and done states must be listed in them",
|
|
395
461
|
path: ["work"],
|
|
396
|
-
})
|
|
462
|
+
})
|
|
463
|
+
.refine((k) => !k.work?.acceptance || k.pins !== undefined, {
|
|
464
|
+
message: "a work kind with acceptance criteria must have pins: a criterion is met by the evidence in its pins field",
|
|
465
|
+
path: ["work", "acceptance"],
|
|
466
|
+
})
|
|
467
|
+
.refine(
|
|
468
|
+
(k) =>
|
|
469
|
+
!k.answers ||
|
|
470
|
+
(k.format === "markdown-front-matter" &&
|
|
471
|
+
k.idField === "id" &&
|
|
472
|
+
k.stateField === "state" &&
|
|
473
|
+
ANSWER_STATES.every((s) => k.states?.includes(s)) &&
|
|
474
|
+
(k.closedStates ?? []).includes("answered")),
|
|
475
|
+
{
|
|
476
|
+
message: 'an answer kind is markdown-front-matter with idField "id", stateField "state", the states escalated, proposed and answered, and answered closed',
|
|
477
|
+
path: ["answers"],
|
|
478
|
+
},
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
/** The states of an answer kind's records (ws-058): open for people, proposed by a model, answered by a table, people, or a confirmed proposal. */
|
|
482
|
+
export const ANSWER_STATES = ["escalated", "proposed", "answered"] as const;
|
|
397
483
|
|
|
398
484
|
export type RecordKind = z.infer<typeof recordKindSchema>;
|
|
399
485
|
|
|
@@ -933,6 +1019,13 @@ export interface RecordEntry {
|
|
|
933
1019
|
reasons: RecordReason[];
|
|
934
1020
|
/** The id of the closed record whose `supersedes` link replaces this one, or null. */
|
|
935
1021
|
supersededBy: string | null;
|
|
1022
|
+
/**
|
|
1023
|
+
* The ids of records whose `remediates` link names this one (#2774), in
|
|
1024
|
+
* path order. Unlike `supersededBy`, several records may remediate the
|
|
1025
|
+
* same one, and a remediation never changes this record's state or
|
|
1026
|
+
* `supersededBy`: it stays exactly as current as it was.
|
|
1027
|
+
*/
|
|
1028
|
+
remediatedBy: string[];
|
|
936
1029
|
/** The record's structured core as JSON (the front matter, or the whole JSON file), or null when it could not be parsed. */
|
|
937
1030
|
data: Record<string, unknown> | null;
|
|
938
1031
|
/**
|
|
@@ -953,6 +1046,8 @@ export interface RecordEntry {
|
|
|
953
1046
|
blockedBy?: WorkLink[];
|
|
954
1047
|
/** For a work kind: each decision the record implements, with its state, or null when no decision has the id. */
|
|
955
1048
|
implements?: WorkLink[];
|
|
1049
|
+
/** For a work kind with acceptance criteria (#2772): how many of the record's criteria passing evidence meets, or null when it lists none. */
|
|
1050
|
+
acceptance?: WorkAcceptance | null;
|
|
956
1051
|
}
|
|
957
1052
|
|
|
958
1053
|
export interface ReadRecordsOptions {
|
|
@@ -1072,6 +1167,20 @@ export function supersedesTargets(kind: Pick<RecordKind, "supersedes">, data: Re
|
|
|
1072
1167
|
return ids.filter((x): x is string => typeof x === "string");
|
|
1073
1168
|
}
|
|
1074
1169
|
|
|
1170
|
+
/**
|
|
1171
|
+
* The ids a record's remediates field names (#2774), read the same way
|
|
1172
|
+
* {@link supersedesTargets} reads `supersedes`.
|
|
1173
|
+
*/
|
|
1174
|
+
export function remediatesTargets(kind: Pick<RecordKind, "remediates">, data: Record<string, unknown> | null): string[] {
|
|
1175
|
+
if (!kind.remediates || data === null) return [];
|
|
1176
|
+
const { field, key } = kind.remediates;
|
|
1177
|
+
const value = data[field];
|
|
1178
|
+
if (key === undefined && typeof value === "string") return [value];
|
|
1179
|
+
if (!Array.isArray(value)) return [];
|
|
1180
|
+
const ids = key === undefined ? value : value.map((l) => (l !== null && typeof l === "object" ? (l as Record<string, unknown>)[key] : undefined));
|
|
1181
|
+
return ids.filter((x): x is string => typeof x === "string");
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1075
1184
|
/** The stem of a file name: the name up to its first `.`, the hash a content-addressed record's name claims (ws-053). */
|
|
1076
1185
|
function nameStem(path: string): string {
|
|
1077
1186
|
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
@@ -1107,6 +1216,7 @@ export async function readRecords(loaded: LoadedRecordKind, options: ReadRecords
|
|
|
1107
1216
|
valid: true,
|
|
1108
1217
|
reasons: [],
|
|
1109
1218
|
supersededBy: null,
|
|
1219
|
+
remediatedBy: [],
|
|
1110
1220
|
data: null,
|
|
1111
1221
|
assets: [],
|
|
1112
1222
|
warnings: [],
|
|
@@ -1226,6 +1336,30 @@ export async function readRecords(loaded: LoadedRecordKind, options: ReadRecords
|
|
|
1226
1336
|
}
|
|
1227
1337
|
}
|
|
1228
1338
|
|
|
1339
|
+
// Remediation (#2774): a later record fixes a consequence of a closed one
|
|
1340
|
+
// without replacing it. The target's status, and its supersededBy, never
|
|
1341
|
+
// change; several records may remediate the same one. Unlike supersedes,
|
|
1342
|
+
// this needs no approval rank: the target is closed or it isn't, and a
|
|
1343
|
+
// still-open target is amended instead of remediated.
|
|
1344
|
+
for (const e of entries) {
|
|
1345
|
+
for (const target of remediatesTargets(kind, e.data)) {
|
|
1346
|
+
const old = byId.get(target);
|
|
1347
|
+
if (!old) {
|
|
1348
|
+
e.reasons.push({ code: "record-remediates-unknown", message: `remediates ${target}, which no record has` });
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
if (old === e) continue;
|
|
1352
|
+
if (old.state === null || !closed.has(old.state)) {
|
|
1353
|
+
e.reasons.push({
|
|
1354
|
+
code: "record-remediates-not-closed",
|
|
1355
|
+
message: `remediates ${target}, which is ${old.state ?? "stateless"}, not closed; a record still open is amended instead`,
|
|
1356
|
+
});
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
if (e.id !== null && !old.remediatedBy.includes(e.id)) old.remediatedBy.push(e.id);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1229
1363
|
// A pin that still matches, at the hash a record this one supersedes
|
|
1230
1364
|
// pinned, while the file has not changed since this record was recorded:
|
|
1231
1365
|
// the decision moved on and the artifact did not (#2549).
|
|
@@ -1250,6 +1384,8 @@ export async function readRecords(loaded: LoadedRecordKind, options: ReadRecords
|
|
|
1250
1384
|
joinSessions(kind, entries, texts, options.subjects ?? null);
|
|
1251
1385
|
// A work kind's links, ready and blocked (#2683), from every record before --current.
|
|
1252
1386
|
const work = kind.work ? await (await import("./work")).applyWork(loaded, entries, options) : undefined;
|
|
1387
|
+
// An answer kind's points (ws-058): an answer whose point is gone or changed is warned about.
|
|
1388
|
+
if (kind.answers) (await import("./points")).applyAnswers(loaded, entries, options);
|
|
1253
1389
|
|
|
1254
1390
|
for (const e of entries) e.valid = e.reasons.length === 0;
|
|
1255
1391
|
const records = options.current ? entries.filter((e) => e.supersededBy === null) : entries;
|