@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
|
@@ -57,12 +57,18 @@ export const REASONS = {
|
|
|
57
57
|
"gates-no-ledger": "The checkout has no chant/lifecycle branch, so there is no gate ledger to read.",
|
|
58
58
|
"gates-no-gate-ledger": "The branch has no gate ledger for the member: no run of it has reached a gate.",
|
|
59
59
|
"gates-ledger-unreadable": "Reading the member's gate ledger failed, so no gate is listed.",
|
|
60
|
+
// A member's stewards status can't fully list (status, #2731).
|
|
61
|
+
"stewards-unreadable": "An *.op.ts file could not be imported, so a steward it declares may be missing.",
|
|
62
|
+
"stewards-conflict": "A steward was dropped: its name, or an Op it lists, belongs to another steward.",
|
|
63
|
+
"steward-runs-unreadable": "Reading an Op's run ledger, or a ConvergeOp's converge ledger, failed, so its last run or last tick is null.",
|
|
60
64
|
// A record that isn't valid (records).
|
|
61
65
|
"record-unparseable": "No front matter, a YAML error or a value outside the JSON subset of YAML, or for a JSON kind a file that is not one object or repeats a member name.",
|
|
62
66
|
"record-schema-invalid": "The record's front matter, or its JSON object, does not match the kind's schema.",
|
|
63
67
|
"record-id-duplicate": "Another record earlier in path order has the same id.",
|
|
64
68
|
"record-supersedes-unknown": "A supersedes link names an id no record has.",
|
|
65
69
|
"record-supersedes-conflict": "A second closed record supersedes a record another one already superseded.",
|
|
70
|
+
"record-remediates-unknown": "A remediates link names an id no record has.",
|
|
71
|
+
"record-remediates-not-closed": "A remediates link names a record that isn't closed; a record still open is amended instead.",
|
|
66
72
|
// A review session that isn't valid (records, #2673).
|
|
67
73
|
"session-seal-mismatch": "A closed session's seal is not the digest of its text: the session changed after it closed.",
|
|
68
74
|
"session-verdict-unknown-record": "A session's verdict names a record that none of the session kind's subject records has.",
|
|
@@ -82,6 +88,12 @@ export const REASONS = {
|
|
|
82
88
|
"work-done-unpinned": "A work record is done and its evidence list is empty: nothing shows the work was done.",
|
|
83
89
|
"work-closed-without-date": "A work record is done or dropped and has no closing date.",
|
|
84
90
|
"work-done-gap-open": "A work record is done, and the finding it came from still fires on its region. graph --intent raises it, and records does by walking that region.",
|
|
91
|
+
"work-acceptance-unmet": "A work record is done, and one of its acceptance criteria has no passing evidence of the verification it expects. check fails on it (WSP117).",
|
|
92
|
+
"work-acceptance-self-verified": "A passing manual verdict on a work record's criterion names the record's implementer, so it does not count: a manual verdict comes from someone else.",
|
|
93
|
+
// An answer to a decision point that is valid but warned about (records, points, ws-058, #2739).
|
|
94
|
+
"answer-points-unreadable": "The points file the answer kind names can't be read, or is not valid.",
|
|
95
|
+
"answer-point-unknown": "The answer's point is not declared in the points file the answer kind names.",
|
|
96
|
+
"answer-point-changed": "The point's declaration changed since the question was asked, so the answer is to an older version of the question.",
|
|
85
97
|
// A verdict the quorum does not count (records, #2671, #2672).
|
|
86
98
|
"review-decider": "The verdict is the decider's own, and the quorum counts verdicts besides the decider's.",
|
|
87
99
|
"review-agent": "The reviewer holds the agent role in the trust policy at base.",
|
|
@@ -121,6 +133,15 @@ export const REASONS = {
|
|
|
121
133
|
// A review given in a session (records review --session, #2693).
|
|
122
134
|
"session-unknown": "--session names no session of a session kind whose subjects are the record's kind.",
|
|
123
135
|
"session-not-open": "--session names a session in a closed state, which takes no more verdicts.",
|
|
136
|
+
// Decision points (points, points ask, points answer, ws-058, #2739).
|
|
137
|
+
"points-undeclared": "No record kind with an answers block is declared, or given with --kind, so there is no points file to ask.",
|
|
138
|
+
"points-invalid": "The points file an answer kind names can't be read, or does not match decision-points.schema.json and the rules checked in code.",
|
|
139
|
+
"point-unknown": "No points file declares the point asked, or the one an answer names.",
|
|
140
|
+
"point-inputs-invalid": "The inputs given to an ask are not a JSON object of the point's declared inputs.",
|
|
141
|
+
"point-decider-failed": "A model decider that fails closed (unreachable: fail) could not answer, so the ask wrote nothing.",
|
|
142
|
+
"answer-not-candidate": "The people's answer is not one of the question's candidates.",
|
|
143
|
+
"quorum-not-met": "Too few of the people who answered count toward the point's quorum: distinct, not holding the agent role, not the steward that asked, and holding one of its roles when it names any.",
|
|
144
|
+
"answer-in-steward-turn": "The answer was given during a steward's turn, or by a process it started: a steward never answers a decision point, and a person answers it through hud or at a shell.",
|
|
124
145
|
// records --since that fails (#2673).
|
|
125
146
|
"since-rev-unknown": "--since names no commit, or a session with no opening revision and no commit that added it.",
|
|
126
147
|
"since-session-unknown": "--since has the shape of a session id and names no commit, and no session the kind or the declaration reads has that id.",
|
|
@@ -148,6 +169,9 @@ export const REASONS = {
|
|
|
148
169
|
"intent-decision-unimplemented": "A decided decision constrains the region, no work item that is not dropped implements it, and no commit falls in its window.",
|
|
149
170
|
"intent-work-blocked": "A work item constraining the region has commits in its window while a work item it needs is not done.",
|
|
150
171
|
"intent-work-open-decided-code": "Commits in the region are a decision's own work while the work item implementing that decision is still open.",
|
|
172
|
+
// The forward coverage check (check --changes, #2773): findings, one per changed path.
|
|
173
|
+
"change-uncovered": "A path the diff changes is covered by no current decided record and no open work item, by path or by its member.",
|
|
174
|
+
"change-out-of-scope": "A record in hand for the change, such as the work item it is for or a decision that item implements, lists a path the diff changes in its out_of_scope.",
|
|
151
175
|
// Composite instances joined to components (graph --composites, #2662): why the list is empty or has no component.
|
|
152
176
|
"composites-no-chant-member": "No member of kind chant was read, so nothing declares a composite instance or a component.",
|
|
153
177
|
"composites-none-declared": "The members read declare no composite instance.",
|
|
@@ -159,6 +183,31 @@ export const REASONS = {
|
|
|
159
183
|
"environments-none-declared": "The member's chant.config.ts declares no environments, so only local and the environments in its ledger are listed.",
|
|
160
184
|
"environments-ledger-undeclared": "The member's ledger has releases in an environment its config's environments don't cover, so chant run --env would refuse it and it is not listed.",
|
|
161
185
|
"environments-ledger-unreadable": "The chant/lifecycle branch exists and the member's ledger environments could not be listed.",
|
|
186
|
+
// A declared diagram's source or render (check, #2764). Each is also a WSP finding.
|
|
187
|
+
"diagram-source-missing": "A declared diagram's source file does not exist in the tree read.",
|
|
188
|
+
"diagram-render-missing": "A declared diagram's render file does not exist in the tree read.",
|
|
189
|
+
"diagram-render-drift": "A declared diagram records a sourceHash, and the source's bytes now hash to something else: the render is stale for its source.",
|
|
190
|
+
// A box that holds a credential (check, #2726). Each is also a WSP finding.
|
|
191
|
+
"box-credential-declared": "A file in a box member's directory carries a literal secret: a credential's shape, or a literal where a credential goes. A variable or secret-manager reference is not one.",
|
|
192
|
+
"box-capability-unbrokered": "A capability in a member's box block names no broker, so the box would hold its credential.",
|
|
193
|
+
// The credential fountain itself hands a persistent box (check, #2780). Also a WSP finding.
|
|
194
|
+
"box-fountain-callback-undeclared": "A box member builds a fountain Box, whose persistent sandbox fountain gives a callback token scoped to its owner, and the member's box block does not declare the fountain-callback capability brokered by fountain with scope owner.",
|
|
195
|
+
// The work lease (chant workspace work claim|renew|release, #2732): why the command could not run.
|
|
196
|
+
"work-kind-missing": "No work kind to find the item in: --kind names a kind with no work block, or the declaration names no work kind.",
|
|
197
|
+
"work-kind-ambiguous": "More than one declared work kind has a record with the id, so --kind must name one.",
|
|
198
|
+
"work-item-unknown": "No work record has the id, so there is nothing to lease.",
|
|
199
|
+
"work-item-closed": "A claim on a work item in a closed state, such as done or dropped: there is no work left to claim.",
|
|
200
|
+
// Evidence attached to a criterion under the run's work lease (#2772): why it was refused. Nothing is written.
|
|
201
|
+
"work-criterion-unknown": "The work record lists no acceptance criterion with the id the evidence names, or its kind has no acceptance criteria.",
|
|
202
|
+
// The work lease: why a claim, renew or release was refused (exit 2).
|
|
203
|
+
"lease-held": "Someone holds a live lease on the work item: another worker, or, for a claim, the same one.",
|
|
204
|
+
"lease-not-held": "Nobody holds a live lease on the work item: it expired, was released or was never claimed.",
|
|
205
|
+
"lease-token-mismatch": "The live lease on the work item carries another fencing token than the one given.",
|
|
206
|
+
"lease-race": "Another writer changed the lease between this command's read and its write.",
|
|
207
|
+
"lease-push-rejected": "The remote refused the lease push: another clone claimed the item first, or the remote could not be reached.",
|
|
208
|
+
// A box whose isolation fails (check, #2727). Each is also a WSP finding.
|
|
209
|
+
"box-isolation-collision": "Two boxes on one host resolve to the same port, state path or cookie name, or two ports in one box share an offset.",
|
|
210
|
+
"box-isolation-literal": "A host's stateRoot or a box's state entry is a literal machine path instead of one derived from an environment reference and the box's name.",
|
|
162
211
|
// The lineage lock (check).
|
|
163
212
|
"lock-invalid": "The lineage lock can't be read.",
|
|
164
213
|
"manual-step-open": "A scope in the lineage lock has an open manual step.",
|
|
@@ -18,6 +18,7 @@ import { constraintCovers, isWorkspacePath, memberHolding, WORKSPACE_PATH_PATTER
|
|
|
18
18
|
import { pinFile, queryRecords, type RecordsDocument } from "./records-cli";
|
|
19
19
|
import { parseFrontMatter, RECORD_WARNING_CODES, SEAL_WARNING_CODES } from "./records";
|
|
20
20
|
import { WORK_WARNING_CODES } from "./work";
|
|
21
|
+
import { ANSWER_WARNING_CODES } from "./points";
|
|
21
22
|
import { workspaceGraph } from "./graph-cli";
|
|
22
23
|
import graphSchema from "./graph.schema.json";
|
|
23
24
|
import recordsSchema from "./records.schema.json";
|
|
@@ -170,8 +171,8 @@ describe("chant workspace records checks each pin", () => {
|
|
|
170
171
|
|
|
171
172
|
test("the schema lists exactly the warning codes", () => {
|
|
172
173
|
// A work kind's records carry the work warnings too (#2683), work-done-gap-open included since records walks a done item's region (#2686),
|
|
173
|
-
// and records adds record-unattested for an author seal under a signers file at base (#2688).
|
|
174
|
-
expect(recordsSchema.$defs.warning.properties.code.enum).toEqual([...RECORD_WARNING_CODES, ...WORK_WARNING_CODES, ...SEAL_WARNING_CODES]);
|
|
174
|
+
// an answer kind's the answer warnings (ws-058), and records adds record-unattested for an author seal under a signers file at base (#2688).
|
|
175
|
+
expect(recordsSchema.$defs.warning.properties.code.enum).toEqual([...RECORD_WARNING_CODES, ...WORK_WARNING_CODES, ...ANSWER_WARNING_CODES, ...SEAL_WARNING_CODES]);
|
|
175
176
|
});
|
|
176
177
|
|
|
177
178
|
test("records pin <path> prints the entry's path from the workspace root and the file's hash", () => {
|
|
@@ -81,7 +81,10 @@ export function checkPins(pins: { path: string; sha256: string }[], tree: Worksp
|
|
|
81
81
|
const state: PinState = actual === null ? "missing" : actual === pin.sha256 ? "pinned" : "drifted";
|
|
82
82
|
assets.push({ ...pin, actual, state });
|
|
83
83
|
if (state === "missing") {
|
|
84
|
-
|
|
84
|
+
// Worded the same for the working tree and a revision (#2745): a missing
|
|
85
|
+
// file is missing either way, and `tree.label` would otherwise make the
|
|
86
|
+
// same finding read differently depending on which one was read.
|
|
87
|
+
warnings.push({ code: "asset-missing", message: `evidence pins ${pin.path}, which does not exist` });
|
|
85
88
|
} else if (state === "drifted") {
|
|
86
89
|
warnings.push({
|
|
87
90
|
code: "asset-drift",
|
|
@@ -24,7 +24,7 @@ import { formatError } from "../cli/format";
|
|
|
24
24
|
import type { CommandContext } from "../cli/registry";
|
|
25
25
|
import { findWorkspaceRoot } from "../project-root";
|
|
26
26
|
import { fileDigest, isWorkspacePath } from "./record-assets";
|
|
27
|
-
import { gitRevisionSource, gitRoot, resolveRevision, workingTreeSource } from "./record-source";
|
|
27
|
+
import { gitRevisionSource, gitRoot, resolveRevision, workingTreeSource, type RecordSource } from "./record-source";
|
|
28
28
|
import { declaredRecordKinds, readDeclaration, WorkspaceReadError, type RecordKindDeclaration } from "./declaration";
|
|
29
29
|
import { declaredKindFile } from "./declared-kinds";
|
|
30
30
|
import { locateWorkspace } from "./which-chant";
|
|
@@ -92,6 +92,13 @@ export type RecordView = RecordEntry & {
|
|
|
92
92
|
/** true when the record's seal verifies for its author against the signers at base; false when it fails, or is missing under an active policy; null when nothing here can say (#2688). */
|
|
93
93
|
attested?: boolean | null;
|
|
94
94
|
attestation?: VerdictAttestation;
|
|
95
|
+
/**
|
|
96
|
+
* For a work kind read in the working tree (#2732): the item's active work
|
|
97
|
+
* lease, or null when nobody holds one. Read from the local lease refs of
|
|
98
|
+
* the ledger owning the kind, without fetching. Absent under `--at`, since a
|
|
99
|
+
* lease is live state and not part of a revision.
|
|
100
|
+
*/
|
|
101
|
+
lease?: { holder: string; token: string; acquiredAt: string; expiresAt: string } | null;
|
|
95
102
|
};
|
|
96
103
|
|
|
97
104
|
/** The role in the trust policy whose holders' verdicts the quorum does not count (#2671). */
|
|
@@ -242,7 +249,15 @@ function gitHistory(top: string, rev: string, prefix: string): RecordHistory {
|
|
|
242
249
|
* with each pin checked in the same tree. Throws a {@link RecordReadError}.
|
|
243
250
|
* `chant workspace graph` and `check` read records through this too.
|
|
244
251
|
*/
|
|
245
|
-
export async function readRecordsFor(
|
|
252
|
+
export async function readRecordsFor(
|
|
253
|
+
query: Omit<RecordsQuery, "base"> & {
|
|
254
|
+
/**
|
|
255
|
+
* Lay more records over a working-tree read, such as the answer records a
|
|
256
|
+
* steward keeps on the lifecycle ledger (#2786). Not called with `at`.
|
|
257
|
+
*/
|
|
258
|
+
overlay?: (source: RecordSource, kind: { loaded: LoadedRecordKind; root: string }) => Promise<RecordSource>;
|
|
259
|
+
},
|
|
260
|
+
): Promise<RecordsRead> {
|
|
246
261
|
// git reports its top through symlinks resolved (/var is /private/var on
|
|
247
262
|
// macOS), so the directory has to be too, or record paths leave the repository.
|
|
248
263
|
const cwd = realpathOr(query.cwd);
|
|
@@ -259,6 +274,7 @@ export async function readRecordsFor(query: Omit<RecordsQuery, "base">): Promise
|
|
|
259
274
|
source = gitRevisionSource(top, at);
|
|
260
275
|
assets = gitTree(top, at, workspaceRoot === "." ? "" : workspaceRoot);
|
|
261
276
|
}
|
|
277
|
+
if (at === null && query.overlay) source = await query.overlay(source, { loaded, root });
|
|
262
278
|
const history = top ? gitHistory(top, at ?? "HEAD", workspaceRoot) : undefined;
|
|
263
279
|
// A session kind's verdicts name records of another kind, read from the same tree (#2673).
|
|
264
280
|
let subjects: { records: RecordEntry[]; reviews: string } | undefined;
|
|
@@ -304,6 +320,11 @@ export async function queryRecords(query: RecordsQuery): Promise<RecordsDocument
|
|
|
304
320
|
...(seals ? authorSeal(loaded.kind, r, policy, seals.checkRecordSeal) : {}),
|
|
305
321
|
}));
|
|
306
322
|
if (loaded.kind.work && query.workGaps !== false && top) await raiseWorkGaps(loaded, records, { root, workspaceRoot, at: query.at });
|
|
323
|
+
if (loaded.kind.work && at === null && top) {
|
|
324
|
+
const { activeWorkLeases } = await import("../lifecycle/work-lease");
|
|
325
|
+
const leases = await activeWorkLeases(loaded.file);
|
|
326
|
+
for (const r of records) if (r.id !== null) r.lease = leases.get(r.id) ?? null;
|
|
327
|
+
}
|
|
307
328
|
return {
|
|
308
329
|
$schema: RECORDS_OUTPUT_SCHEMA_ID,
|
|
309
330
|
contract: RECORDS_CONTRACT_VERSION,
|
|
@@ -590,13 +611,15 @@ function formatRecords(records: RecordView[], summary: { total: number; valid: n
|
|
|
590
611
|
const title = typeof r.data?.title === "string" ? r.data.title : r.path;
|
|
591
612
|
const flag = r.valid ? "" : " INVALID";
|
|
592
613
|
const superseded = r.supersededBy ? ` superseded by ${r.supersededBy}` : "";
|
|
614
|
+
const remediated = r.remediatedBy.length > 0 ? ` remediated by ${r.remediatedBy.join(", ")}` : "";
|
|
593
615
|
const attested = r.provenance.level === "attested" ? ` attested by ${r.provenance.principal}` : "";
|
|
594
616
|
const sealed = r.attested === true ? ` sealed by ${(r.data?.[RECORD_SEAL_FIELD] as { signer: string }).signer}` : "";
|
|
595
|
-
lines.push(`${(r.id ?? "-").padEnd(idWidth)} ${(r.state ?? "-").padEnd(stateWidth)} ${title}${superseded}${attested}${sealed}${flag}`);
|
|
617
|
+
lines.push(`${(r.id ?? "-").padEnd(idWidth)} ${(r.state ?? "-").padEnd(stateWidth)} ${title}${superseded}${remediated}${attested}${sealed}${flag}`);
|
|
596
618
|
if (r.ready !== undefined) {
|
|
597
619
|
const blocked = (r.blockedBy ?? []).map((b) => `${b.id} (${b.state ?? "unknown"})`).join(", ");
|
|
598
620
|
const implemented = (r.implements ?? []).map((d) => `${d.id} (${d.state ?? "unknown"})`).join(", ");
|
|
599
|
-
const
|
|
621
|
+
const held = r.lease ? `held by ${r.lease.holder} until ${r.lease.expiresAt}` : "";
|
|
622
|
+
const status = [r.ready ? "ready" : blocked ? `blocked by ${blocked}` : "", implemented ? `implements ${implemented}` : "", held].filter(Boolean).join("; ");
|
|
600
623
|
if (status) lines.push(`${" ".repeat(idWidth + 2)}${status}`);
|
|
601
624
|
}
|
|
602
625
|
for (const reason of r.reasons) lines.push(`${" ".repeat(idWidth + 2)}${reason.code}: ${reason.message} (${r.path})`);
|
|
@@ -620,7 +643,7 @@ export async function runWorkspaceUnknown(ctx: CommandContext): Promise<number>
|
|
|
620
643
|
console.error(
|
|
621
644
|
formatError({
|
|
622
645
|
message: sub ? `Unknown workspace subcommand: ${sub}` : "chant workspace needs a subcommand",
|
|
623
|
-
hint: `Workspace subcommands: audit, build, check, graph, init, lineage, lint, ls, records, status, upgrade, verify. Run "chant --help" for their options.`,
|
|
646
|
+
hint: `Workspace subcommands: audit, build, check, graph, init, lineage, lint, ls, records, status, upgrade, verify, work. Run "chant --help" for their options.`,
|
|
624
647
|
}),
|
|
625
648
|
);
|
|
626
649
|
return 1;
|
|
@@ -13,6 +13,7 @@ import { afterAll, describe, expect, test } from "vitest";
|
|
|
13
13
|
import { queryRecords, RECORDS_CONTRACT_VERSION, RECORDS_OUTPUT_SCHEMA_ID, type RecordsDocument } from "./records-cli";
|
|
14
14
|
import { READ_ERROR_CODES, RECORD_REASON_CODES, RECORD_WARNING_CODES, recordTextDigest, REVIEW_REASON_CODES, SEAL_WARNING_CODES } from "./records";
|
|
15
15
|
import { WORK_WARNING_CODES } from "./work";
|
|
16
|
+
import { ANSWER_WARNING_CODES } from "./points";
|
|
16
17
|
import schema from "./records.schema.json";
|
|
17
18
|
import { PROVENANCE_LEVELS } from "./trust/attestor";
|
|
18
19
|
|
|
@@ -149,8 +150,8 @@ describe("records output schema", () => {
|
|
|
149
150
|
|
|
150
151
|
test("lists exactly the warning codes the code can return", () => {
|
|
151
152
|
// A work kind's records carry the work warnings too (#2683), work-done-gap-open included since records walks a done item's region (#2686),
|
|
152
|
-
// and records adds record-unattested for an author seal under a signers file at base (#2688).
|
|
153
|
-
expect(schema.$defs.warning.properties.code.enum).toEqual([...RECORD_WARNING_CODES, ...WORK_WARNING_CODES, ...SEAL_WARNING_CODES]);
|
|
153
|
+
// an answer kind's the answer warnings (ws-058), and records adds record-unattested for an author seal under a signers file at base (#2688).
|
|
154
|
+
expect(schema.$defs.warning.properties.code.enum).toEqual([...RECORD_WARNING_CODES, ...WORK_WARNING_CODES, ...ANSWER_WARNING_CODES, ...SEAL_WARNING_CODES]);
|
|
154
155
|
});
|
|
155
156
|
|
|
156
157
|
test("every failure validates with its code", async () => {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessions from a UI (#2693), through the command line: `records close` with
|
|
3
|
+
* the declared session kind, and `records --since <session id>` without
|
|
4
|
+
* --kind. Four CLI spawns put it near 13s on CI, close to the unit shards'
|
|
5
|
+
* per-test budget, so it runs in the test-e2e job (#2817). The rest of #2693
|
|
6
|
+
* is in records-sessions-write.test.ts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { pathToFileURL } from "node:url";
|
|
12
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
13
|
+
import { cleanScratch, commitAll, contract, git, REPO } from "./__fixtures__/contract-repo";
|
|
14
|
+
import { declareSessions, newSessionFields, sessionsRepo, SESSIONS_KIND } from "./__fixtures__/sessions";
|
|
15
|
+
import closeSchema from "./records-close.schema.json";
|
|
16
|
+
import { type RecordsSinceDocument } from "./records-since";
|
|
17
|
+
import sinceSchema from "./records-since.schema.json";
|
|
18
|
+
import { newRecord } from "./records-write";
|
|
19
|
+
import newSchema from "./records-new.schema.json";
|
|
20
|
+
|
|
21
|
+
afterAll(cleanScratch);
|
|
22
|
+
|
|
23
|
+
const close = contract(closeSchema);
|
|
24
|
+
const since = contract(sinceSchema);
|
|
25
|
+
const created = contract(newSchema);
|
|
26
|
+
|
|
27
|
+
/** The reference fixture with a declaration, committed. */
|
|
28
|
+
function declared(): string {
|
|
29
|
+
const root = sessionsRepo();
|
|
30
|
+
declareSessions(root);
|
|
31
|
+
commitAll(root, "declare");
|
|
32
|
+
return root;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Open S-0002 through records new, and commit it. */
|
|
36
|
+
async function opened(root: string): Promise<{ path: string; head: string }> {
|
|
37
|
+
const head = git(root, "rev-parse", "HEAD");
|
|
38
|
+
const doc = await newRecord({ kind: SESSIONS_KIND, fields: newSessionFields(), cwd: root });
|
|
39
|
+
created.expectValid(doc);
|
|
40
|
+
if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
|
|
41
|
+
expect(doc.id).toBe("S-0002");
|
|
42
|
+
commitAll(root, "open S-0002");
|
|
43
|
+
return { path: doc.path, head };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("records --since <session id> (#2693)", () => {
|
|
47
|
+
test(
|
|
48
|
+
"through the CLI: close with the declared session kind, and --since <session id> without --kind",
|
|
49
|
+
async () => {
|
|
50
|
+
const root = declared();
|
|
51
|
+
await opened(root);
|
|
52
|
+
const run = (...args: string[]) =>
|
|
53
|
+
spawnSync(process.execPath, ["--import", pathToFileURL(join(REPO, "node_modules/tsx/dist/loader.mjs")).href, join(REPO, "packages/core/src/cli/main.ts"), "workspace", "records", ...args], {
|
|
54
|
+
cwd: root,
|
|
55
|
+
encoding: "utf-8",
|
|
56
|
+
env: { ...process.env, NO_COLOR: "1" },
|
|
57
|
+
timeout: 60_000,
|
|
58
|
+
});
|
|
59
|
+
const closed = run("close", "S-0002");
|
|
60
|
+
expect(closed.status, closed.stderr).toBe(0);
|
|
61
|
+
close.expectValid(JSON.parse(closed.stdout));
|
|
62
|
+
const again = run("close", "S-0002");
|
|
63
|
+
expect(again.status).toBe(1);
|
|
64
|
+
expect(JSON.parse(again.stdout).error.code).toBe("record-closed");
|
|
65
|
+
commitAll(root, "close");
|
|
66
|
+
const set = run("--since", "S-0002", "--json");
|
|
67
|
+
expect(set.status, set.stderr).toBe(0);
|
|
68
|
+
const kinds = JSON.parse(set.stdout).kinds as RecordsSinceDocument[];
|
|
69
|
+
for (const k of kinds) since.expectValid(k);
|
|
70
|
+
expect(kinds.map((k) => ("session" in k ? k.session?.id : null))).toEqual(["S-0002", "S-0002"]);
|
|
71
|
+
const text = run("--kind", SESSIONS_KIND, "--since", "S-0002");
|
|
72
|
+
expect(text.stdout).toContain("session S-0002 (closed)");
|
|
73
|
+
expect(text.stdout).toContain("S-0002 new, closed");
|
|
74
|
+
},
|
|
75
|
+
120_000,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
@@ -5,13 +5,12 @@
|
|
|
5
5
|
* is not open and appends the verdict to the session too, and
|
|
6
6
|
* `records --since <session id>` compares the session's own revisions. The
|
|
7
7
|
* fixture is a copy of the reference workspace's decisions and design
|
|
8
|
-
* member with a declaration naming both kinds.
|
|
8
|
+
* member with a declaration naming both kinds. The CLI run of close and
|
|
9
|
+
* --since is in records-sessions-write.e2e.test.ts (#2817).
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
import { spawnSync } from "node:child_process";
|
|
12
12
|
import { cpSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
13
|
import { join } from "node:path";
|
|
14
|
-
import { pathToFileURL } from "node:url";
|
|
15
14
|
import { afterAll, describe, expect, test } from "vitest";
|
|
16
15
|
import { cleanScratch, commitAll, contract, git, REPO, scratchDir } from "./__fixtures__/contract-repo";
|
|
17
16
|
import { declareSessions, DECISIONS_KIND, newSessionFields, sessionsRepo, SESSIONS_KIND } from "./__fixtures__/sessions";
|
|
@@ -240,35 +239,4 @@ describe("records --since <session id> (#2693)", () => {
|
|
|
240
239
|
git(root, "tag", "v-1");
|
|
241
240
|
expect((await sinceDoc({ kind: DECISIONS_KIND, since: "v-1", cwd: root })).session).toBeUndefined();
|
|
242
241
|
});
|
|
243
|
-
|
|
244
|
-
test(
|
|
245
|
-
"through the CLI: close with the declared session kind, and --since <session id> without --kind",
|
|
246
|
-
async () => {
|
|
247
|
-
const root = declared();
|
|
248
|
-
await opened(root);
|
|
249
|
-
const run = (...args: string[]) =>
|
|
250
|
-
spawnSync(process.execPath, ["--import", pathToFileURL(join(REPO, "node_modules/tsx/dist/loader.mjs")).href, join(REPO, "packages/core/src/cli/main.ts"), "workspace", "records", ...args], {
|
|
251
|
-
cwd: root,
|
|
252
|
-
encoding: "utf-8",
|
|
253
|
-
env: { ...process.env, NO_COLOR: "1" },
|
|
254
|
-
timeout: 60_000,
|
|
255
|
-
});
|
|
256
|
-
const closed = run("close", "S-0002");
|
|
257
|
-
expect(closed.status, closed.stderr).toBe(0);
|
|
258
|
-
close.expectValid(JSON.parse(closed.stdout));
|
|
259
|
-
const again = run("close", "S-0002");
|
|
260
|
-
expect(again.status).toBe(1);
|
|
261
|
-
expect(JSON.parse(again.stdout).error.code).toBe("record-closed");
|
|
262
|
-
commitAll(root, "close");
|
|
263
|
-
const set = run("--since", "S-0002", "--json");
|
|
264
|
-
expect(set.status, set.stderr).toBe(0);
|
|
265
|
-
const kinds = JSON.parse(set.stdout).kinds as RecordsSinceDocument[];
|
|
266
|
-
for (const k of kinds) since.expectValid(k);
|
|
267
|
-
expect(kinds.map((k) => ("session" in k ? k.session?.id : null))).toEqual(["S-0002", "S-0002"]);
|
|
268
|
-
const text = run("--kind", SESSIONS_KIND, "--since", "S-0002");
|
|
269
|
-
expect(text.stdout).toContain("session S-0002 (closed)");
|
|
270
|
-
expect(text.stdout).toContain("S-0002 new, closed");
|
|
271
|
-
},
|
|
272
|
-
120_000,
|
|
273
|
-
);
|
|
274
242
|
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant workspace records new|amend|review` (#2670) through the command
|
|
3
|
+
* line: standard input, exit codes and the refusal document, from the CLI a
|
|
4
|
+
* user runs. Each test spawns the CLI several times, which put them near 20s
|
|
5
|
+
* on CI, over the unit shards' per-test budget, so they run in the test-e2e
|
|
6
|
+
* job (#2817). The commands' own behaviour is covered in records-write.test.ts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { pathToFileURL } from "node:url";
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
15
|
+
import { parseFrontMatter } from "./records";
|
|
16
|
+
import { renderRecord } from "./records-write";
|
|
17
|
+
|
|
18
|
+
const REPO = join(import.meta.dirname, "..", "..", "..", "..");
|
|
19
|
+
const DECISIONS = join(REPO, "docs", "design", "decisions");
|
|
20
|
+
const KIND = "decisions/decision.kind.mjs";
|
|
21
|
+
|
|
22
|
+
type Data = Record<string, unknown>;
|
|
23
|
+
|
|
24
|
+
const SAMPLE = (() => {
|
|
25
|
+
const fm = parseFrontMatter(readFileSync(join(DECISIONS, "ws-003-seal-scope.md"), "utf-8"));
|
|
26
|
+
if (!fm.ok) throw new Error(fm.message);
|
|
27
|
+
return fm.value;
|
|
28
|
+
})();
|
|
29
|
+
|
|
30
|
+
/** A decided decision like ws-003, with `over` laid on top. */
|
|
31
|
+
function decision(over: Data = {}): Data {
|
|
32
|
+
return { ...structuredClone(SAMPLE), ...over };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A proposed decision: no choice, nobody decided. */
|
|
36
|
+
function proposal(over: Data = {}): Data {
|
|
37
|
+
return decision({ state: "proposed", choice: null, decided_by: null, decided_on: null, ...over });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let dir: string;
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
dir = realpathSync(mkdtempSync(join(tmpdir(), "chant-records-write-")));
|
|
43
|
+
mkdirSync(join(dir, "decisions"));
|
|
44
|
+
cpSync(join(DECISIONS, "decision.kind.mjs"), join(dir, "decisions", "decision.kind.mjs"));
|
|
45
|
+
cpSync(join(DECISIONS, "decision.schema.json"), join(dir, "decisions", "decision.schema.json"));
|
|
46
|
+
put("ws-001-one.md", decision({ id: "ws-001", title: "One" }));
|
|
47
|
+
put("ws-002-two.md", proposal({ id: "ws-002", title: "Two" }));
|
|
48
|
+
put("ws-003-three.md", decision({ id: "ws-003", title: "Three", state: "ratified" }));
|
|
49
|
+
});
|
|
50
|
+
afterEach(() => rmSync(dir, { recursive: true, force: true }));
|
|
51
|
+
|
|
52
|
+
function put(name: string, data: Data): void {
|
|
53
|
+
writeFileSync(join(dir, "decisions", name), renderRecord(data, `\n# ${String(data.title)}\n`));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe("the command line", () => {
|
|
57
|
+
const cli = (args: string[], input?: string) => {
|
|
58
|
+
const loader = pathToFileURL(join(REPO, "node_modules", "tsx", "dist", "loader.mjs")).href;
|
|
59
|
+
try {
|
|
60
|
+
const out = execFileSync(process.execPath, ["--import", loader, join(REPO, "packages", "core", "src", "cli", "main.ts"), "workspace", "records", ...args], {
|
|
61
|
+
cwd: dir,
|
|
62
|
+
input,
|
|
63
|
+
encoding: "utf-8",
|
|
64
|
+
env: { ...process.env, TSX_DISABLE_CACHE: "1" },
|
|
65
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
66
|
+
});
|
|
67
|
+
return { status: 0, doc: JSON.parse(out) };
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const e = err as { status: number; stdout: string };
|
|
70
|
+
return { status: e.status, doc: JSON.parse(e.stdout) };
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
test(
|
|
75
|
+
"new reads fields from standard input, review appends, and a refusal exits 1 with its code",
|
|
76
|
+
() => {
|
|
77
|
+
const { id: _id, ...rest } = decision({ title: "From stdin" });
|
|
78
|
+
const made = cli(["new", KIND, "--from", "-"], JSON.stringify(rest));
|
|
79
|
+
expect(made).toMatchObject({ status: 0, doc: { id: "ws-004", path: "decisions/ws-004-from-stdin.md" } });
|
|
80
|
+
expect(existsSync(join(dir, "decisions", "ws-004-from-stdin.md"))).toBe(true);
|
|
81
|
+
const reviewed = cli(["review", "ws-004", "--kind", KIND, "--verdict", "agree", "--by", "alice"]);
|
|
82
|
+
expect(reviewed).toMatchObject({ status: 0, doc: { review: { reviewer: "alice", verdict: "agree" } } });
|
|
83
|
+
const refused = cli(["review", "ws-004", "--kind", KIND, "--verdict", "dissent", "--by", "bob"]);
|
|
84
|
+
expect(refused).toMatchObject({ status: 1, doc: { error: { code: "review-note-required" } } });
|
|
85
|
+
writeFileSync(join(dir, "patch.json"), JSON.stringify({ title: "Changed" }));
|
|
86
|
+
expect(cli(["amend", "ws-004", "--kind", KIND, "--set", "patch.json"])).toMatchObject({ status: 1, doc: { error: { code: "amend-supersede-instead" } } });
|
|
87
|
+
expect(cli(["amend", "ws-004", "--set", "patch.json"])).toMatchObject({ status: 1, doc: { error: { code: "write-usage-invalid" } } });
|
|
88
|
+
},
|
|
89
|
+
120_000,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
test(
|
|
93
|
+
"new --by writes the proposer, not the decider, and amend --by is refused rather than dropped (#2756)",
|
|
94
|
+
() => {
|
|
95
|
+
const { id: _id, ...rest } = decision({ title: "Kept by a session", state: "proposed", choice: null, decided_by: null, decided_on: null });
|
|
96
|
+
const made = cli(["new", KIND, "--from", "-", "--by", "hud-session"], JSON.stringify(rest));
|
|
97
|
+
expect(made).toMatchObject({ status: 0, doc: { id: "ws-004" } });
|
|
98
|
+
const fm = parseFrontMatter(readFileSync(join(dir, "decisions", "ws-004-kept-by-a-session.md"), "utf-8"));
|
|
99
|
+
if (!fm.ok) throw new Error(fm.message);
|
|
100
|
+
expect(fm.value).toMatchObject({ proposed_by: "hud-session", decided_by: null });
|
|
101
|
+
|
|
102
|
+
writeFileSync(join(dir, "patch.json"), JSON.stringify({ title: "Changed" }));
|
|
103
|
+
const refused = cli(["amend", "ws-004", "--kind", KIND, "--set", "patch.json", "--by", "alice"]);
|
|
104
|
+
expect(refused).toMatchObject({ status: 1, doc: { error: { code: "write-usage-invalid", message: expect.stringContaining("--by is not taken by amend") } } });
|
|
105
|
+
},
|
|
106
|
+
120_000,
|
|
107
|
+
);
|
|
108
|
+
});
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
* `chant workspace records new|amend|review` (#2670): each command validates
|
|
3
3
|
* before it writes, writes exactly one file or none, and refuses with a closed
|
|
4
4
|
* code. Run in a directory outside git holding a copy of the decision kind.
|
|
5
|
+
* The same commands through the CLI are in records-write.e2e.test.ts (#2817).
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import { execFileSync } from "node:child_process";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
-
import { cpSync,
|
|
9
|
+
import { cpSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
-
import { pathToFileURL } from "node:url";
|
|
13
12
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
14
13
|
import { parseFrontMatter, recordTextDigest } from "./records";
|
|
15
14
|
import { allocateId, amendRecord, newRecord, renderRecord, reviewRecord, toYaml } from "./records-write";
|
|
@@ -187,7 +186,7 @@ describe("records new", () => {
|
|
|
187
186
|
});
|
|
188
187
|
|
|
189
188
|
test("keeps the width of the ids it follows", () => {
|
|
190
|
-
const entry = (id: string) => ({ id, path: `d/${id}-x.md`, state: null, valid: true, reasons: [], supersededBy: null, data: {}, digest: "", assets: [], warnings: [] });
|
|
189
|
+
const entry = (id: string) => ({ id, path: `d/${id}-x.md`, state: null, valid: true, reasons: [], supersededBy: null, remediatedBy: [], data: {}, digest: "", assets: [], warnings: [] });
|
|
191
190
|
expect(allocateId([entry("s-0009"), entry("s-0010")], undefined, "k")).toBe("s-0011");
|
|
192
191
|
expect(allocateId([entry("s-999")], undefined, "k")).toBe("s-1000");
|
|
193
192
|
// Case and padding come from the records (#2683).
|
|
@@ -225,6 +224,35 @@ describe("records new", () => {
|
|
|
225
224
|
test("a kind that can't be read is its read error", async () => {
|
|
226
225
|
expect(code(await newRecord({ kind: "missing.kind.mjs", fields: fields(fresh()), cwd: dir }))).toBe("kind-unreadable");
|
|
227
226
|
});
|
|
227
|
+
|
|
228
|
+
test("--by names the proposer of a record that opens proposed, apart from decided_by (#2756)", async () => {
|
|
229
|
+
const { id: _id, ...freshProposal } = proposal({ title: "Filed by someone" });
|
|
230
|
+
const proposed = await newRecord({ kind: KIND, fields: fields(freshProposal), by: "carol", cwd: dir });
|
|
231
|
+
expect(code(proposed)).toBe("ok");
|
|
232
|
+
const proposedPath = (proposed as { path: string }).path.replace("decisions/", "");
|
|
233
|
+
expect(data(proposedPath)).toMatchObject({ proposed_by: "carol", decided_by: null });
|
|
234
|
+
|
|
235
|
+
// A conflicting proposed_by given in the fields is refused, same as a conflicting decided_by is today.
|
|
236
|
+
const conflict = await newRecord({ kind: KIND, fields: fields({ ...freshProposal, title: "Two proposers", proposed_by: "dave" }), by: "carol", cwd: dir });
|
|
237
|
+
expect(code(conflict)).toBe("write-input-invalid");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("--by names the decider when a new record opens straight into a later state", async () => {
|
|
241
|
+
const { id: _id, decided_by: _db, ...freshDecided } = decision({ title: "Decided at once" });
|
|
242
|
+
const decidedDoc = await newRecord({ kind: KIND, fields: fields({ ...freshDecided, decided_by: null }), by: "dana", cwd: dir });
|
|
243
|
+
expect(code(decidedDoc)).toBe("ok");
|
|
244
|
+
const decidedPath = (decidedDoc as { path: string }).path.replace("decisions/", "");
|
|
245
|
+
expect(data(decidedPath)).toMatchObject({ decided_by: "dana" });
|
|
246
|
+
expect(data(decidedPath)).not.toHaveProperty("proposed_by");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("--by is refused for a kind whose new record opens decided and declares no reviews", async () => {
|
|
250
|
+
const kindFile = join(dir, "decisions", "decision.kind.mjs");
|
|
251
|
+
writeFileSync(kindFile, readFileSync(kindFile, "utf-8").replace(/^\s*reviews: \{[^}]*\},?\n/m, ""));
|
|
252
|
+
const { id: _id, decided_by: _db, ...freshDecided } = decision({ title: "No reviews declared" });
|
|
253
|
+
const doc = await newRecord({ kind: KIND, fields: fields({ ...freshDecided, decided_by: null }), by: "erin", cwd: dir });
|
|
254
|
+
expect(code(doc)).toBe("write-usage-invalid");
|
|
255
|
+
});
|
|
228
256
|
});
|
|
229
257
|
|
|
230
258
|
describe("records amend", () => {
|
|
@@ -336,40 +364,3 @@ describe("records review", () => {
|
|
|
336
364
|
expect(code(await reviewRecord({ kind: KIND, id: "ws-001", verdict: "agree", by: "a", cwd: dir }))).toBe("review-unsupported");
|
|
337
365
|
});
|
|
338
366
|
});
|
|
339
|
-
|
|
340
|
-
describe("the command line", () => {
|
|
341
|
-
const cli = (args: string[], input?: string) => {
|
|
342
|
-
const loader = pathToFileURL(join(REPO, "node_modules", "tsx", "dist", "loader.mjs")).href;
|
|
343
|
-
try {
|
|
344
|
-
const out = execFileSync(process.execPath, ["--import", loader, join(REPO, "packages", "core", "src", "cli", "main.ts"), "workspace", "records", ...args], {
|
|
345
|
-
cwd: dir,
|
|
346
|
-
input,
|
|
347
|
-
encoding: "utf-8",
|
|
348
|
-
env: { ...process.env, TSX_DISABLE_CACHE: "1" },
|
|
349
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
350
|
-
});
|
|
351
|
-
return { status: 0, doc: JSON.parse(out) };
|
|
352
|
-
} catch (err) {
|
|
353
|
-
const e = err as { status: number; stdout: string };
|
|
354
|
-
return { status: e.status, doc: JSON.parse(e.stdout) };
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
test(
|
|
359
|
-
"new reads fields from standard input, review appends, and a refusal exits 1 with its code",
|
|
360
|
-
() => {
|
|
361
|
-
const { id: _id, ...rest } = decision({ title: "From stdin" });
|
|
362
|
-
const made = cli(["new", KIND, "--from", "-"], JSON.stringify(rest));
|
|
363
|
-
expect(made).toMatchObject({ status: 0, doc: { id: "ws-004", path: "decisions/ws-004-from-stdin.md" } });
|
|
364
|
-
expect(existsSync(join(dir, "decisions", "ws-004-from-stdin.md"))).toBe(true);
|
|
365
|
-
const reviewed = cli(["review", "ws-004", "--kind", KIND, "--verdict", "agree", "--by", "alice"]);
|
|
366
|
-
expect(reviewed).toMatchObject({ status: 0, doc: { review: { reviewer: "alice", verdict: "agree" } } });
|
|
367
|
-
const refused = cli(["review", "ws-004", "--kind", KIND, "--verdict", "dissent", "--by", "bob"]);
|
|
368
|
-
expect(refused).toMatchObject({ status: 1, doc: { error: { code: "review-note-required" } } });
|
|
369
|
-
writeFileSync(join(dir, "patch.json"), JSON.stringify({ title: "Changed" }));
|
|
370
|
-
expect(cli(["amend", "ws-004", "--kind", KIND, "--set", "patch.json"])).toMatchObject({ status: 1, doc: { error: { code: "amend-supersede-instead" } } });
|
|
371
|
-
expect(cli(["amend", "ws-004", "--set", "patch.json"])).toMatchObject({ status: 1, doc: { error: { code: "write-usage-invalid" } } });
|
|
372
|
-
},
|
|
373
|
-
120_000,
|
|
374
|
-
);
|
|
375
|
-
});
|