@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
package/src/workspace/status.ts
CHANGED
|
@@ -24,7 +24,11 @@
|
|
|
24
24
|
* The JSON also lists each member's gates, read from its gate ledger on the
|
|
25
25
|
* same branch (#2674, `status-gates.ts`): the state of each, the approvals
|
|
26
26
|
* that count, and the `chant approve` line that answers it. The text view
|
|
27
|
-
* doesn't show them.
|
|
27
|
+
* doesn't show them. So does each member's box block (#2726): the
|
|
28
|
+
* capabilities it reaches through a broker, with the broker and the scope a
|
|
29
|
+
* broker enforces, and, when the block names a host, the box's ports, state
|
|
30
|
+
* paths and cookie names resolved from its identity (#2727,
|
|
31
|
+
* `box-isolation.ts`), so a runtime reads them instead of choosing its own.
|
|
28
32
|
*/
|
|
29
33
|
|
|
30
34
|
import { execFileSync } from "node:child_process";
|
|
@@ -35,10 +39,14 @@ import type { CommandContext } from "../cli/registry";
|
|
|
35
39
|
import { GATES_DIR } from "../lifecycle/gate-ledger";
|
|
36
40
|
import { readPathSha } from "../lifecycle/git";
|
|
37
41
|
import { latestPerComponent, readReleaseLedger, type ReleaseRecord } from "../lifecycle/release-ledger";
|
|
42
|
+
import { readReleasePlan, type ReleasePlan } from "../lifecycle/plan-ledger";
|
|
43
|
+
import { listWorkLeases, type WorkLeaseState } from "../lifecycle/work-lease";
|
|
38
44
|
import { findWorkspaceRoot } from "../project-root";
|
|
39
|
-
import {
|
|
45
|
+
import { resolveBoxes, type ResolvedIsolation } from "./box-isolation";
|
|
46
|
+
import { declaredRecordKinds, readDeclaration, readerVersion, WorkspaceReadError, type Declaration, type ErrorLocation, type Member } from "./declaration";
|
|
40
47
|
import type { ReasonCode } from "./reason-codes";
|
|
41
48
|
import { GATE_REASON_CODES, readMemberGates, type GateLedgerReader, type StatusGate, type StatusGateLedger } from "./status-gates";
|
|
49
|
+
import { STEWARD_REASON_CODES, readMemberStewards, type StatusSteward, type StewardReasonCode } from "./status-stewards";
|
|
42
50
|
import { gitTop, workingTree } from "./tree";
|
|
43
51
|
import { handToRootChant } from "./which-chant";
|
|
44
52
|
|
|
@@ -71,6 +79,9 @@ export type StatusReasonCode = (typeof STATUS_REASON_CODES)[number];
|
|
|
71
79
|
/** Why a member's gates can't be listed (#2674). Closed, like {@link STATUS_REASON_CODES}. */
|
|
72
80
|
export const STATUS_GATE_REASON_CODES = GATE_REASON_CODES;
|
|
73
81
|
|
|
82
|
+
/** Why a member's stewards can't be fully listed (#2731). Closed, like {@link STATUS_REASON_CODES}. */
|
|
83
|
+
export const STATUS_STEWARD_REASON_CODES = STEWARD_REASON_CODES;
|
|
84
|
+
|
|
74
85
|
/**
|
|
75
86
|
* Why the status couldn't be read at all. The declaration's own codes, except
|
|
76
87
|
* the two that only `--at` returns, and one for the environment name.
|
|
@@ -104,6 +115,15 @@ export interface StatusRelease {
|
|
|
104
115
|
actor: string;
|
|
105
116
|
/** Read flags, such as `legacy-digest` (#2514). */
|
|
106
117
|
flags: string[];
|
|
118
|
+
/**
|
|
119
|
+
* The release plan `digest` names, read from `_plans/<digest>.json` on
|
|
120
|
+
* chant/lifecycle (ws-055, #2733) — the work items and evidence this
|
|
121
|
+
* release shipped, without the runner's own files. Null when this
|
|
122
|
+
* checkout has no plan stored under that digest: an ordinary component
|
|
123
|
+
* release (most releases carry no plan at all), or a plan this checkout
|
|
124
|
+
* hasn't fetched yet.
|
|
125
|
+
*/
|
|
126
|
+
plan: ReleasePlan | null;
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
export interface StatusLedger {
|
|
@@ -145,6 +165,44 @@ export interface StatusMember {
|
|
|
145
165
|
gateLedger: StatusGateLedger;
|
|
146
166
|
/** Each gate in the member's gate ledger, one per environment asked for, sorted by component then gate. */
|
|
147
167
|
gates: StatusGate[];
|
|
168
|
+
/** The member's box block as declared, or null when it declares none (#2726). A broker reads the scopes it enforces here. */
|
|
169
|
+
box: StatusBox | null;
|
|
170
|
+
/** The stewards the member declares (#2731), with their form in `env`, their Ops and each Op's last run. Sorted by name. */
|
|
171
|
+
stewards: StatusSteward[];
|
|
172
|
+
/** Why a steward or a last run may be missing from `stewards`. */
|
|
173
|
+
stewardReasons: { code: StewardReasonCode; message: string }[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** A box's brokered capabilities, from the declaration (#2726). */
|
|
177
|
+
export interface StatusBox {
|
|
178
|
+
capabilities: { name: string; broker: string | null; scope: string[] }[];
|
|
179
|
+
/** The box's ports, state paths and cookie names, resolved from its identity, or null when the block declares no host (#2727). */
|
|
180
|
+
isolation: ResolvedIsolation | null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* One work lease (#2732): a work item's lease ref in the ledger of the member
|
|
185
|
+
* that owns its work kind, read from the local refs without fetching.
|
|
186
|
+
*/
|
|
187
|
+
export interface StatusLease extends Omit<WorkLeaseState, "ref"> {
|
|
188
|
+
/** The member whose ledger holds the lease, or null for the flat ledger (the root member, or a kind no member owns). */
|
|
189
|
+
member: string | null;
|
|
190
|
+
ref: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* One work item's acceptance criteria, counted (#2772): each current record
|
|
195
|
+
* of a declared work kind with acceptance criteria that lists some.
|
|
196
|
+
*/
|
|
197
|
+
export interface StatusAcceptance {
|
|
198
|
+
/** The member that declares the work kind, or null for the workspace's own. */
|
|
199
|
+
member: string | null;
|
|
200
|
+
/** The work kind file, from the workspace root. */
|
|
201
|
+
kind: string;
|
|
202
|
+
item: string;
|
|
203
|
+
state: string | null;
|
|
204
|
+
met: number;
|
|
205
|
+
total: number;
|
|
148
206
|
}
|
|
149
207
|
|
|
150
208
|
export type StatusDocument =
|
|
@@ -157,6 +215,10 @@ export type StatusDocument =
|
|
|
157
215
|
lifecycle: { ref: string; commit: string | null };
|
|
158
216
|
workspace: { name: string; root: string; file: string };
|
|
159
217
|
members: StatusMember[];
|
|
218
|
+
/** Every work lease in the workspace's ledgers, active and expired, by member then item (#2732). A released lease has no ref and is not listed. */
|
|
219
|
+
leases: StatusLease[];
|
|
220
|
+
/** Each work item's acceptance criteria, met of total, by declared kind then item (#2772). Empty when no declared work kind has criteria. */
|
|
221
|
+
acceptance: StatusAcceptance[];
|
|
160
222
|
summary: { members: number; released: number; unreadable: number; differing: number | null };
|
|
161
223
|
}
|
|
162
224
|
| {
|
|
@@ -179,6 +241,8 @@ export interface StatusQuery {
|
|
|
179
241
|
readGates?: GateLedgerReader;
|
|
180
242
|
/** The instant a gate's expiry is measured against; now by default. */
|
|
181
243
|
now?: string;
|
|
244
|
+
/** Reads one member's stewards; the declaration and ledger reader unless a test swaps it. */
|
|
245
|
+
readStewards?: typeof readMemberStewards;
|
|
182
246
|
}
|
|
183
247
|
|
|
184
248
|
class StatusError extends Error {
|
|
@@ -240,8 +304,16 @@ async function readEnvironment(member: Member, env: string, cwd: string, read: L
|
|
|
240
304
|
reason: { code: "ledger-unreadable", message: `${ledger.path}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` },
|
|
241
305
|
};
|
|
242
306
|
}
|
|
243
|
-
|
|
244
|
-
|
|
307
|
+
// A member's plans live beside its other lifecycle stores (#2524 D7,
|
|
308
|
+
// #2538): _members/<member>/_plans/ for the "members" layout, the flat
|
|
309
|
+
// top-level _plans/ for "flat" — the same split ledger.path already
|
|
310
|
+
// resolved above for releases.jsonl, mirrored here since readReleasePlan
|
|
311
|
+
// reads through one process for every member without changing cwd per
|
|
312
|
+
// member (see plan-ledger.ts's readReleasePlan doc).
|
|
313
|
+
const planPrefix = ledger.layout === "members" ? `${MEMBERS_DIR}/${member.name}/` : "";
|
|
314
|
+
const latest = [...latestPerComponent(records).values()].sort((a, b) => (a.component < b.component ? -1 : a.component > b.component ? 1 : 0));
|
|
315
|
+
const releases: StatusRelease[] = await Promise.all(
|
|
316
|
+
latest.map(async (r) => ({
|
|
245
317
|
component: r.component,
|
|
246
318
|
digest: r.digest,
|
|
247
319
|
gitSha: r.gitSha,
|
|
@@ -250,8 +322,9 @@ async function readEnvironment(member: Member, env: string, cwd: string, read: L
|
|
|
250
322
|
timestamp: r.timestamp,
|
|
251
323
|
actor: r.actor,
|
|
252
324
|
flags: [...(r.flags ?? [])],
|
|
253
|
-
|
|
254
|
-
|
|
325
|
+
plan: await readReleasePlan(r.digest, { cwd, prefix: planPrefix }),
|
|
326
|
+
})),
|
|
327
|
+
);
|
|
255
328
|
return {
|
|
256
329
|
env,
|
|
257
330
|
ledger: { ...ledger, shared: false },
|
|
@@ -303,12 +376,14 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
|
|
|
303
376
|
const commit = lifecycleTip(found.dir);
|
|
304
377
|
const now = query.now ?? new Date().toISOString();
|
|
305
378
|
|
|
379
|
+
const isolation = new Map(resolveBoxes(declaration).map((b) => [b.member.name, b.isolation]));
|
|
306
380
|
const members: StatusMember[] = [];
|
|
307
381
|
for (const m of declaration.members) {
|
|
308
382
|
const environments: StatusEnvironment[] = [];
|
|
309
383
|
for (const env of envs) environments.push(await readEnvironment(m, env, found.dir, read));
|
|
310
384
|
const own = await hasMemberLedger(m, found.dir);
|
|
311
385
|
const gates = await readMemberGates(own ? `${MEMBERS_DIR}/${m.name}/${GATES_DIR}` : GATES_DIR, own ? "members" : "flat", commit, envs, found.dir, now, query.readGates);
|
|
386
|
+
const stewards = await (query.readStewards ?? readMemberStewards)(resolve(found.dir, m.dir), query.env, now, m.kind, m.box);
|
|
312
387
|
members.push({
|
|
313
388
|
name: m.name,
|
|
314
389
|
dir: m.dir,
|
|
@@ -318,6 +393,15 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
|
|
|
318
393
|
readable: environments.every((e) => e.reason === null),
|
|
319
394
|
gateLedger: gates.ledger,
|
|
320
395
|
gates: gates.gates,
|
|
396
|
+
box:
|
|
397
|
+
m.box === null
|
|
398
|
+
? null
|
|
399
|
+
: {
|
|
400
|
+
capabilities: m.box.capabilities.map((c) => ({ name: c.name, broker: c.broker, scope: [...c.scope] })),
|
|
401
|
+
isolation: isolation.get(m.name) ?? null,
|
|
402
|
+
},
|
|
403
|
+
stewards: stewards.stewards,
|
|
404
|
+
stewardReasons: stewards.reasons,
|
|
321
405
|
});
|
|
322
406
|
}
|
|
323
407
|
// Several members read from one flat ledger see the same records; say so.
|
|
@@ -327,6 +411,9 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
|
|
|
327
411
|
const flatGates = members.filter((m) => m.gateLedger.layout === "flat").length;
|
|
328
412
|
for (const m of members) m.gateLedger.shared = m.gateLedger.layout === "flat" && flatGates > 1;
|
|
329
413
|
|
|
414
|
+
const leases = await readWorkspaceLeases(declaration.members, found.dir, new Date(now));
|
|
415
|
+
const acceptance = await readWorkspaceAcceptance(declaration, found.dir);
|
|
416
|
+
|
|
330
417
|
return {
|
|
331
418
|
...head,
|
|
332
419
|
env: query.env,
|
|
@@ -334,6 +421,8 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
|
|
|
334
421
|
lifecycle: { ref: LIFECYCLE_REF, commit },
|
|
335
422
|
workspace: { name: declaration.name, root: rootDir === "" ? "." : rootDir, file: declaration.file },
|
|
336
423
|
members,
|
|
424
|
+
leases,
|
|
425
|
+
acceptance,
|
|
337
426
|
summary: {
|
|
338
427
|
members: members.length,
|
|
339
428
|
released: members.filter((m) => m.environments[0].releases.length > 0).length,
|
|
@@ -351,6 +440,31 @@ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocumen
|
|
|
351
440
|
}
|
|
352
441
|
}
|
|
353
442
|
|
|
443
|
+
/**
|
|
444
|
+
* The work leases of the flat ledger and of each member's (#2732). Leases are
|
|
445
|
+
* env-independent, so the environment asked for doesn't narrow them. Never
|
|
446
|
+
* fetches: the remote's leases are the ones last fetched.
|
|
447
|
+
*/
|
|
448
|
+
async function readWorkspaceLeases(members: Member[], cwd: string, now: Date): Promise<StatusLease[]> {
|
|
449
|
+
const ledgers: { member: string | null; prefix: string }[] = [{ member: null, prefix: "" }];
|
|
450
|
+
for (const m of members) if (m.dir !== ".") ledgers.push({ member: m.name, prefix: `${MEMBERS_DIR}/${m.name}/` });
|
|
451
|
+
const out: StatusLease[] = [];
|
|
452
|
+
for (const l of ledgers) {
|
|
453
|
+
for (const lease of await listWorkLeases({ cwd, memberPrefix: l.prefix, now })) out.push({ ...lease, member: l.member });
|
|
454
|
+
}
|
|
455
|
+
return out;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** The acceptance counts of every declared work kind with criteria (#2772), read in the working tree. */
|
|
459
|
+
async function readWorkspaceAcceptance(declaration: Declaration, root: string): Promise<StatusAcceptance[]> {
|
|
460
|
+
if (declaredRecordKinds(declaration).length === 0) return [];
|
|
461
|
+
const { loadDeclaredKinds } = await import("./declared-kinds");
|
|
462
|
+
const kinds = await loadDeclaredKinds(declaration, workingTree(root), root, { acceptance: true });
|
|
463
|
+
return kinds.flatMap((k) =>
|
|
464
|
+
(k.acceptance ?? []).map((a) => ({ member: k.declared.member, kind: k.declared.path, item: a.item, state: a.state, met: a.met, total: a.total })),
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
354
468
|
export async function runWorkspaceStatus(ctx: CommandContext): Promise<number> {
|
|
355
469
|
const { args } = ctx;
|
|
356
470
|
const env = args.extraPositional;
|
|
@@ -434,6 +548,28 @@ export function formatStatus(doc: Extract<StatusDocument, { members: unknown }>)
|
|
|
434
548
|
for (const n of notes.get(i) ?? []) lines.push(n);
|
|
435
549
|
});
|
|
436
550
|
}
|
|
551
|
+
if (doc.leases.length > 0) {
|
|
552
|
+
lines.push("");
|
|
553
|
+
const rows: string[][] = [["WORK ITEM", "HOLDER", "EXPIRES", "STATE"]];
|
|
554
|
+
for (const l of doc.leases) rows.push([l.member ? `${l.member}/${l.item}` : l.item, l.holder, l.expiresAt, l.state]);
|
|
555
|
+
lines.push(...table(rows));
|
|
556
|
+
}
|
|
557
|
+
if (doc.acceptance.length > 0) {
|
|
558
|
+
lines.push("");
|
|
559
|
+
const rows: string[][] = [["WORK ITEM", "STATE", "CRITERIA MET"]];
|
|
560
|
+
for (const a of doc.acceptance) rows.push([a.member ? `${a.member}/${a.item}` : a.item, a.state ?? "-", `${a.met}/${a.total}`]);
|
|
561
|
+
lines.push(...table(rows));
|
|
562
|
+
}
|
|
563
|
+
const boxes = doc.members.filter((m) => m.box?.isolation);
|
|
564
|
+
if (boxes.length > 0) {
|
|
565
|
+
lines.push("");
|
|
566
|
+
const rows: string[][] = [["BOX", "HOST", "SLOT", "PORTS", "STATE"]];
|
|
567
|
+
for (const m of boxes) {
|
|
568
|
+
const i = m.box!.isolation!;
|
|
569
|
+
rows.push([m.name, i.host, String(i.slot), `${i.portRange.from}-${i.portRange.to}`, i.stateDir]);
|
|
570
|
+
}
|
|
571
|
+
lines.push(...table(rows));
|
|
572
|
+
}
|
|
437
573
|
const s = doc.summary;
|
|
438
574
|
lines.push("");
|
|
439
575
|
lines.push(
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acceptance criteria on work records (#2772), on a workspace built from the
|
|
3
|
+
* reference workspace's decision and work kinds in a throwaway git repository.
|
|
4
|
+
*
|
|
5
|
+
* - W-001 is done, and passing evidence of each criterion's verification
|
|
6
|
+
* meets both of them, the manual one by bob while alice owns the item: met.
|
|
7
|
+
* - W-002 is done, and its only evidence is a unit test for an e2e
|
|
8
|
+
* criterion: unmet.
|
|
9
|
+
* - W-003 is done, and its manual criterion's verdict is by alice, its owner:
|
|
10
|
+
* the verdict does not count, so it is unmet as well.
|
|
11
|
+
* - W-004 is done and lists no criteria: a legacy item, read as before.
|
|
12
|
+
* - W-005 is in progress. A steward's Op attaches evidence to it under its
|
|
13
|
+
* work lease, and is refused on the manual criterion.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
19
|
+
import { cleanScratch, contract, git, REPO, repo } from "./__fixtures__/contract-repo";
|
|
20
|
+
import { runDeclarationChecks } from "./checks";
|
|
21
|
+
import { listWorkspaceWithKinds } from "./ls";
|
|
22
|
+
import lsSchema from "./ls.schema.json";
|
|
23
|
+
import { queryRecords } from "./records-cli";
|
|
24
|
+
import recordsSchema from "./records.schema.json";
|
|
25
|
+
import { workspaceStatus } from "./status";
|
|
26
|
+
import statusSchema from "./status.schema.json";
|
|
27
|
+
import { attachWorkEvidence } from "./work-evidence";
|
|
28
|
+
import workEvidenceSchema from "./work-evidence.schema.json";
|
|
29
|
+
import { workEvidence } from "../op/builders";
|
|
30
|
+
import { workEvidence as workEvidenceActivity } from "../op/activities";
|
|
31
|
+
import { ACTIVITY_PROFILES } from "../op/activity-profiles";
|
|
32
|
+
import { OpRunFailure, runOpLocally, type OpRunResult } from "../op/local-executor";
|
|
33
|
+
import type { ActivityFn } from "../op/activity-registry";
|
|
34
|
+
import type { OpConfig } from "../op/types";
|
|
35
|
+
import { declareSteward } from "../op/steward";
|
|
36
|
+
import { claimWorkLease, releaseWorkLease } from "../lifecycle/work-lease";
|
|
37
|
+
|
|
38
|
+
const REF = join(REPO, "reference-workspace");
|
|
39
|
+
const ref = (path: string) => readFileSync(join(REF, path), "utf-8");
|
|
40
|
+
|
|
41
|
+
function work(id: string, fields: Record<string, unknown>): string {
|
|
42
|
+
const data = {
|
|
43
|
+
schema: 1,
|
|
44
|
+
id,
|
|
45
|
+
title: `Work ${id}`,
|
|
46
|
+
state: "done",
|
|
47
|
+
implements: [],
|
|
48
|
+
needs: [],
|
|
49
|
+
constrains: ["member:app"],
|
|
50
|
+
evidence: [],
|
|
51
|
+
owner: "alice",
|
|
52
|
+
opened_on: "2026-09-25",
|
|
53
|
+
closed_on: "2026-09-25",
|
|
54
|
+
source: { kind: "workspace", member: "app" },
|
|
55
|
+
supersedes: [],
|
|
56
|
+
...fields,
|
|
57
|
+
};
|
|
58
|
+
return `---\n${JSON.stringify(data, null, 2)}\n---\n\n# ${id}\n`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const link = (criterion: string, verification: string, result: string, extra: Record<string, unknown> = {}) => ({
|
|
62
|
+
title: `${criterion} ${verification}`,
|
|
63
|
+
url: `https://example.com/${criterion}`,
|
|
64
|
+
criterion,
|
|
65
|
+
verification,
|
|
66
|
+
result,
|
|
67
|
+
...extra,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const KIND = "work/work.kind.mjs";
|
|
71
|
+
let root: string;
|
|
72
|
+
|
|
73
|
+
beforeAll(() => {
|
|
74
|
+
root = repo({
|
|
75
|
+
"chant.workspace.json": JSON.stringify(
|
|
76
|
+
{
|
|
77
|
+
name: "studio",
|
|
78
|
+
schema: 1,
|
|
79
|
+
members: [{ name: "app", dir: "app", kind: "other", because: "a plain Node server" }],
|
|
80
|
+
records: [{ kind: "decisions/decision.kind.mjs" }, { kind: KIND }],
|
|
81
|
+
},
|
|
82
|
+
null,
|
|
83
|
+
2,
|
|
84
|
+
),
|
|
85
|
+
"app/server.mjs": "export const port = 8080;\n",
|
|
86
|
+
"decisions/decision.kind.mjs": ref("decisions/decision.kind.mjs"),
|
|
87
|
+
"decisions/decision.schema.json": ref("decisions/decision.schema.json"),
|
|
88
|
+
"decisions/ref-001-how-the-app-is-deployed.md": ref("decisions/ref-001-how-the-app-is-deployed.md"),
|
|
89
|
+
[KIND]: ref(KIND),
|
|
90
|
+
"work/work.schema.json": ref("work/work.schema.json"),
|
|
91
|
+
"work/W-001-met.md": work("W-001", {
|
|
92
|
+
acceptance: [
|
|
93
|
+
{ id: "AC-1", text: "The server answers on its port", verification: "unit" },
|
|
94
|
+
{ id: "AC-2", text: "A designer finds the page matches the spec", verification: "manual" },
|
|
95
|
+
],
|
|
96
|
+
evidence: [link("AC-1", "unit", "fail"), link("AC-1", "unit", "pass"), link("AC-2", "manual", "pass", { by: "bob" })],
|
|
97
|
+
}),
|
|
98
|
+
"work/W-002-unmet.md": work("W-002", {
|
|
99
|
+
acceptance: [{ id: "AC-1", text: "The page loads in a browser", verification: "e2e" }],
|
|
100
|
+
evidence: [link("AC-1", "unit", "pass")],
|
|
101
|
+
}),
|
|
102
|
+
"work/W-003-self.md": work("W-003", {
|
|
103
|
+
acceptance: [{ id: "AC-1", text: "Someone else signs off", verification: "manual" }],
|
|
104
|
+
evidence: [link("AC-1", "manual", "pass", { by: "Alice" })],
|
|
105
|
+
}),
|
|
106
|
+
"work/W-004-legacy.md": work("W-004", { evidence: [{ title: "The review", url: "https://example.com/review" }] }),
|
|
107
|
+
"work/W-005-leased.md": work("W-005", {
|
|
108
|
+
state: "in-progress",
|
|
109
|
+
closed_on: null,
|
|
110
|
+
acceptance: [
|
|
111
|
+
{ id: "AC-1", text: "The server's test passes", verification: "unit" },
|
|
112
|
+
{ id: "AC-2", text: "A person tries it", verification: "manual" },
|
|
113
|
+
],
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
git(root, "config", "user.name", "t");
|
|
117
|
+
git(root, "config", "user.email", "t@example.com");
|
|
118
|
+
git(root, "config", "commit.gpgsign", "false");
|
|
119
|
+
git(root, "add", "-A");
|
|
120
|
+
git(root, "commit", "-q", "-m", "the work");
|
|
121
|
+
});
|
|
122
|
+
afterAll(cleanScratch);
|
|
123
|
+
|
|
124
|
+
const records = contract(recordsSchema);
|
|
125
|
+
|
|
126
|
+
async function read() {
|
|
127
|
+
const doc = await queryRecords({ kind: join(root, KIND), cwd: root, workGaps: false });
|
|
128
|
+
records.expectValid(doc);
|
|
129
|
+
if ("error" in doc || !("records" in doc)) throw new Error(JSON.stringify(doc));
|
|
130
|
+
return doc.records;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const codes = (r: { warnings: { code: string }[] }) => r.warnings.map((w) => w.code);
|
|
134
|
+
|
|
135
|
+
describe("acceptance criteria on read (#2772)", () => {
|
|
136
|
+
test("met: each criterion has passing evidence of its verification, and the manual verdict is someone else's", async () => {
|
|
137
|
+
const w1 = (await read()).find((r) => r.id === "W-001")!;
|
|
138
|
+
expect(w1.valid).toBe(true);
|
|
139
|
+
expect(w1.acceptance).toEqual({
|
|
140
|
+
met: 2,
|
|
141
|
+
total: 2,
|
|
142
|
+
criteria: [
|
|
143
|
+
{ id: "AC-1", verification: "unit", met: true },
|
|
144
|
+
{ id: "AC-2", verification: "manual", met: true },
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
expect(codes(w1)).toEqual([]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("unmet: evidence of another verification does not count, and the done item is warned", async () => {
|
|
151
|
+
const w2 = (await read()).find((r) => r.id === "W-002")!;
|
|
152
|
+
expect(w2.acceptance).toMatchObject({ met: 0, total: 1 });
|
|
153
|
+
expect(codes(w2)).toEqual(["work-acceptance-unmet"]);
|
|
154
|
+
expect(w2.warnings[0].message).toMatch(/AC-1 \(e2e\)/);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("manual by the implementer: the verdict is refused, warned, and the criterion stays unmet", async () => {
|
|
158
|
+
const w3 = (await read()).find((r) => r.id === "W-003")!;
|
|
159
|
+
expect(w3.acceptance).toMatchObject({ met: 0, total: 1 });
|
|
160
|
+
expect(codes(w3)).toEqual(["work-acceptance-self-verified", "work-acceptance-unmet"]);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("a legacy item without criteria is valid, reads acceptance null and gets no acceptance warning", async () => {
|
|
164
|
+
const w4 = (await read()).find((r) => r.id === "W-004")!;
|
|
165
|
+
expect(w4.valid).toBe(true);
|
|
166
|
+
expect(w4.acceptance).toBeNull();
|
|
167
|
+
expect(codes(w4)).toEqual([]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("an open item with criteria unmet is not warned: only done needs them met", async () => {
|
|
171
|
+
const w5 = (await read()).find((r) => r.id === "W-005")!;
|
|
172
|
+
expect(w5.acceptance).toMatchObject({ met: 0, total: 2 });
|
|
173
|
+
expect(codes(w5)).toEqual([]);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("check fails with WSP117 and the reason code on each done item with a criterion unmet", async () => {
|
|
177
|
+
const report = await runDeclarationChecks(root);
|
|
178
|
+
const wsp117 = report.diagnostics.filter((d) => d.ruleId === "WSP117");
|
|
179
|
+
expect(wsp117.map((d) => [d.file, d.code, d.severity])).toEqual([
|
|
180
|
+
["work/W-002-unmet.md", "work-acceptance-unmet", "error"],
|
|
181
|
+
["work/W-003-self.md", "work-acceptance-unmet", "error"],
|
|
182
|
+
]);
|
|
183
|
+
expect(report.ok).toBe(false);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("ls --json and status --json count each item's criteria, met of total, in the read contract", async () => {
|
|
187
|
+
const ls = await listWorkspaceWithKinds({ cwd: root });
|
|
188
|
+
contract(lsSchema).expectValid(ls);
|
|
189
|
+
if ("error" in ls) throw new Error(ls.error.message);
|
|
190
|
+
const kinds = Object.fromEntries(ls.workspace.records.map((r) => [r.path, r.acceptance]));
|
|
191
|
+
expect(kinds["decisions/decision.kind.mjs"]).toBeNull();
|
|
192
|
+
expect(kinds[KIND]).toEqual([
|
|
193
|
+
{ item: "W-001", state: "done", met: 2, total: 2 },
|
|
194
|
+
{ item: "W-002", state: "done", met: 0, total: 1 },
|
|
195
|
+
{ item: "W-003", state: "done", met: 0, total: 1 },
|
|
196
|
+
{ item: "W-005", state: "in-progress", met: 0, total: 2 },
|
|
197
|
+
]);
|
|
198
|
+
|
|
199
|
+
const status = await workspaceStatus({ cwd: root, env: "dev" });
|
|
200
|
+
contract(statusSchema).expectValid(status);
|
|
201
|
+
if ("error" in status) throw new Error(status.error.message);
|
|
202
|
+
expect(status.acceptance.map((a) => [a.member, a.kind, a.item, `${a.met}/${a.total}`])).toEqual([
|
|
203
|
+
[null, KIND, "W-001", "2/2"],
|
|
204
|
+
[null, KIND, "W-002", "0/1"],
|
|
205
|
+
[null, KIND, "W-003", "0/1"],
|
|
206
|
+
[null, KIND, "W-005", "0/2"],
|
|
207
|
+
]);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("a steward Op under workLease attaches evidence to a criterion (#2772)", () => {
|
|
212
|
+
const evidenceDoc = contract(workEvidenceSchema);
|
|
213
|
+
|
|
214
|
+
async function run(config: OpConfig, holder: string): Promise<OpRunResult> {
|
|
215
|
+
const activities = new Map<string, ActivityFn>([["workEvidence", workEvidenceActivity as unknown as ActivityFn]]);
|
|
216
|
+
try {
|
|
217
|
+
return await runOpLocally(config, activities, ACTIVITY_PROFILES, undefined, { cwd: root, ledger: { cwd: root }, work: { holder } });
|
|
218
|
+
} catch (err) {
|
|
219
|
+
if (err instanceof OpRunFailure) return err.result;
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const op = (step: ReturnType<typeof workEvidence>): OpConfig => ({
|
|
225
|
+
name: "verify",
|
|
226
|
+
overview: "attach the test run to its criterion",
|
|
227
|
+
workLease: { item: "W-005", kind: KIND },
|
|
228
|
+
phases: [{ name: "Verify", steps: [step] }],
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("the run holding the lease appends passing evidence, and the criterion is met", async () => {
|
|
232
|
+
const config = op(workEvidence("AC-1", { result: "pass", title: "the server test", url: "https://example.com/ci/1", cwd: root }));
|
|
233
|
+
expect(declareSteward({ name: "box", ops: [config] }).ops).toHaveLength(1);
|
|
234
|
+
const result = await run(config, "box/verify@h1");
|
|
235
|
+
expect(result.status).toBe("ok");
|
|
236
|
+
expect(result.workLease).toMatchObject({ item: "W-005", released: true });
|
|
237
|
+
const w5 = (await read()).find((r) => r.id === "W-005")!;
|
|
238
|
+
expect(w5.acceptance).toMatchObject({ met: 1, total: 2 });
|
|
239
|
+
expect(w5.data!.evidence).toEqual([
|
|
240
|
+
expect.objectContaining({ url: "https://example.com/ci/1", criterion: "AC-1", verification: "unit", result: "pass", by: "box/verify@h1" }),
|
|
241
|
+
]);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("a manual criterion is refused: the run holding the lease is the implementer", async () => {
|
|
245
|
+
const result = await run(op(workEvidence("AC-2", { result: "pass", title: "tried it", url: "https://example.com/tried", cwd: root })), "box/verify@h2");
|
|
246
|
+
expect(result.status).toBe("fail");
|
|
247
|
+
expect(JSON.stringify(result.records)).toMatch(/work-acceptance-self-verified/);
|
|
248
|
+
expect((await read()).find((r) => r.id === "W-005")!.acceptance).toMatchObject({ met: 1, total: 2 });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("without the live lease, or with another token or criterion, nothing is written", async () => {
|
|
252
|
+
const ask = (over: Partial<Parameters<typeof attachWorkEvidence>[0]>) =>
|
|
253
|
+
attachWorkEvidence({ cwd: root, item: "W-005", holder: "me", token: "t", criterion: "AC-1", result: "pass", title: "x", url: "https://example.com/x", ...over });
|
|
254
|
+
const unleased = await ask({});
|
|
255
|
+
evidenceDoc.expectValid(unleased);
|
|
256
|
+
expect("error" in unleased && unleased.error.code).toBe("lease-not-held");
|
|
257
|
+
|
|
258
|
+
const claimed = await claimWorkLease("W-005", "me", { cwd: join(root, "work") });
|
|
259
|
+
if (!claimed.ok) throw new Error(claimed.message);
|
|
260
|
+
try {
|
|
261
|
+
expect(await ask({ token: "other" })).toMatchObject({ error: { code: "lease-token-mismatch" } });
|
|
262
|
+
expect(await ask({ holder: "you", token: claimed.lease.token })).toMatchObject({ error: { code: "lease-held" } });
|
|
263
|
+
expect(await ask({ token: claimed.lease.token, criterion: "AC-9" })).toMatchObject({ error: { code: "work-criterion-unknown" } });
|
|
264
|
+
const ok = await ask({ token: claimed.lease.token, result: "fail" });
|
|
265
|
+
evidenceDoc.expectValid(ok);
|
|
266
|
+
expect(ok).toMatchObject({ item: "W-005", kind: KIND, path: "work/W-005-leased.md", acceptance: { met: 1, total: 2 } });
|
|
267
|
+
} finally {
|
|
268
|
+
await releaseWorkLease("W-005", "me", { cwd: join(root, "work"), token: claimed.lease.token });
|
|
269
|
+
}
|
|
270
|
+
expect(await attachWorkEvidence({ cwd: root, item: "W-004", holder: "me", token: "t", criterion: "AC-1", result: "pass", title: "x", url: "https://example.com/x" })).toMatchObject({
|
|
271
|
+
error: { code: "work-item-closed" },
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|