@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
|
@@ -5,6 +5,13 @@ import { join } from "node:path";
|
|
|
5
5
|
import type { ParsedArgs } from "../registry";
|
|
6
6
|
|
|
7
7
|
const discoverOpsMock = vi.fn();
|
|
8
|
+
// Default: no steward owns anything discovered, so `stewardTurnGate` (#2750)
|
|
9
|
+
// is a no-op for every test that doesn't explicitly set up a steward — a
|
|
10
|
+
// `mockResolvedValueOnce` in those tests overrides it for one call and this
|
|
11
|
+
// default is what every other call sees.
|
|
12
|
+
const discoverStewardsMock = vi.fn().mockResolvedValue({ stewards: new Map(), errors: [], conflicts: [] });
|
|
13
|
+
const acquireStewardTurnMock = vi.fn();
|
|
14
|
+
const releaseLeaseMock = vi.fn();
|
|
8
15
|
const loadChantConfigMock = vi.fn();
|
|
9
16
|
const writeFileSyncMock = vi.fn();
|
|
10
17
|
const mkdirSyncMock = vi.fn();
|
|
@@ -18,7 +25,18 @@ const recordGateApprovalMock = vi.fn();
|
|
|
18
25
|
const { memoryGateLedgerPort } = await vi.importActual<typeof import("../../op/gate")>("../../op/gate");
|
|
19
26
|
let gateLedger = memoryGateLedgerPort();
|
|
20
27
|
|
|
21
|
-
vi.mock("../../op/discover", () => ({
|
|
28
|
+
vi.mock("../../op/discover", () => ({
|
|
29
|
+
discoverOps: () => discoverOpsMock(),
|
|
30
|
+
discoverStewards: () => discoverStewardsMock(),
|
|
31
|
+
}));
|
|
32
|
+
vi.mock("../../op/operator", async (importOriginal) => {
|
|
33
|
+
const actual = await importOriginal<typeof import("../../op/operator")>();
|
|
34
|
+
return { ...actual, acquireStewardTurn: (...args: unknown[]) => acquireStewardTurnMock(...args) };
|
|
35
|
+
});
|
|
36
|
+
vi.mock("../../lifecycle/lease", async (importOriginal) => {
|
|
37
|
+
const actual = await importOriginal<typeof import("../../lifecycle/lease")>();
|
|
38
|
+
return { ...actual, releaseLease: (...args: unknown[]) => releaseLeaseMock(...args) };
|
|
39
|
+
});
|
|
22
40
|
vi.mock("../../config", async () => {
|
|
23
41
|
const actual = await vi.importActual<typeof import("../../config")>("../../config");
|
|
24
42
|
return { ...actual, loadChantConfig: (...args: unknown[]) => loadChantConfigMock(...args) };
|
|
@@ -199,6 +217,25 @@ describe("runOp dispatcher", () => {
|
|
|
199
217
|
stderrWrite.mockRestore();
|
|
200
218
|
});
|
|
201
219
|
|
|
220
|
+
test("--work on an Op with no work lease → exit 1, nothing run (#2748)", async () => {
|
|
221
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map([makeOp("hello")]), errors: [] });
|
|
222
|
+
const stderr = makeStderrSpy();
|
|
223
|
+
const exit = await runOp({ args: makeArgs({ path: "hello", work: "W-1" }), plugins: [], serializers: [] });
|
|
224
|
+
expect(exit).toBe(1);
|
|
225
|
+
expect(stderr.join("\n")).toContain('--work W-1: Op "hello" declares no work lease');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("an Op whose work lease leaves the item to the run, run without --work → exit 1 naming the flag (#2748)", async () => {
|
|
229
|
+
discoverOpsMock.mockResolvedValue({
|
|
230
|
+
ops: new Map([["leased", { config: { name: "leased", overview: "leased", phases: [], workLease: {} } }]]),
|
|
231
|
+
errors: [],
|
|
232
|
+
});
|
|
233
|
+
const stderr = makeStderrSpy();
|
|
234
|
+
const exit = await runOp({ args: makeArgs({ path: "leased", holder: "me" }), plugins: [], serializers: [] });
|
|
235
|
+
expect(exit).toBe(1);
|
|
236
|
+
expect(stderr.join("\n")).toContain("chant run leased --work <id>");
|
|
237
|
+
});
|
|
238
|
+
|
|
202
239
|
test("--report → exit 1 naming the removal, nothing run (#2116)", async () => {
|
|
203
240
|
discoverOpsMock.mockResolvedValue({ ops: new Map([makeOp("hello")]), errors: [] });
|
|
204
241
|
const stderr = makeStderrSpy();
|
|
@@ -269,6 +306,138 @@ describe("runOp dispatcher", () => {
|
|
|
269
306
|
});
|
|
270
307
|
});
|
|
271
308
|
|
|
309
|
+
/**
|
|
310
|
+
* chant #2750 — an Op a declared steward lists is one of its turns, not a
|
|
311
|
+
* run beside one: `chant run <op>` for such an Op takes the steward's turn
|
|
312
|
+
* lease (in the local form) or is redirected to the steward's thread (on
|
|
313
|
+
* fountain), rather than running unaware of a turn already in progress.
|
|
314
|
+
*
|
|
315
|
+
* `acquireStewardTurn` and `releaseLease` are stubbed here (mocked above):
|
|
316
|
+
* the point of these tests is the CLI's own decision and its message, not
|
|
317
|
+
* the lease's git plumbing, which `op/steward.test.ts` and
|
|
318
|
+
* `lifecycle/lease.test.ts` already cover against a real repo.
|
|
319
|
+
*/
|
|
320
|
+
describe("runOp: a steward's turn (#2750)", () => {
|
|
321
|
+
function stewardOwning(opName: string, name: string, form: "local" | "fountain" = "local") {
|
|
322
|
+
return {
|
|
323
|
+
stewards: new Map([[name, {
|
|
324
|
+
declaration: {
|
|
325
|
+
kind: "Chant::Steward", name, ops: [{ name: opName, phases: [] }],
|
|
326
|
+
form: { default: form, environments: {} }, capabilities: [], vault: null,
|
|
327
|
+
},
|
|
328
|
+
filePath: "ops/steward.op.ts", exportName: "steward",
|
|
329
|
+
}]]),
|
|
330
|
+
errors: [], conflicts: [],
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
beforeEach(() => {
|
|
335
|
+
discoverOpsMock.mockReset();
|
|
336
|
+
loadChantConfigMock.mockReset().mockResolvedValue({ config: {} });
|
|
337
|
+
loadPluginsMock.mockReset().mockResolvedValue([]);
|
|
338
|
+
acquireStewardTurnMock.mockReset();
|
|
339
|
+
releaseLeaseMock.mockReset().mockResolvedValue(true);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("local form, the turn is free: takes it, runs, and releases it once the run is over", async () => {
|
|
343
|
+
discoverOpsMock.mockResolvedValue({
|
|
344
|
+
ops: new Map([localOp("release", [{ kind: "activity", fn: "shellCmd", args: { cmd: "true" } }])]),
|
|
345
|
+
errors: [],
|
|
346
|
+
});
|
|
347
|
+
discoverStewardsMock.mockResolvedValueOnce(stewardOwning("release", "box-steward"));
|
|
348
|
+
acquireStewardTurnMock.mockResolvedValue({
|
|
349
|
+
acquired: true,
|
|
350
|
+
lease: { op: "_turns/box-steward", holder: "h", token: "t1", acquiredAt: "x", expiresAt: "y" },
|
|
351
|
+
});
|
|
352
|
+
const stderrWrite = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
353
|
+
const exit = await runOp({ args: makeArgs({ path: "release" }), plugins: [], serializers: [] });
|
|
354
|
+
stderrWrite.mockRestore();
|
|
355
|
+
|
|
356
|
+
expect(exit).toBe(0);
|
|
357
|
+
expect(acquireStewardTurnMock).toHaveBeenCalledTimes(1);
|
|
358
|
+
expect(acquireStewardTurnMock.mock.calls[0]?.[0]).toBe("box-steward");
|
|
359
|
+
expect(releaseLeaseMock).toHaveBeenCalledTimes(1);
|
|
360
|
+
expect(releaseLeaseMock).toHaveBeenCalledWith("_turns/box-steward", expect.any(String), "t1");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("local form, a turn is already in progress: refused, naming the steward and holder, nothing runs", async () => {
|
|
364
|
+
discoverOpsMock.mockResolvedValue({
|
|
365
|
+
ops: new Map([localOp("release", [{ kind: "activity", fn: "shellCmd", args: { cmd: "true" } }])]),
|
|
366
|
+
errors: [],
|
|
367
|
+
});
|
|
368
|
+
discoverStewardsMock.mockResolvedValueOnce(stewardOwning("release", "box-steward"));
|
|
369
|
+
acquireStewardTurnMock.mockResolvedValue({
|
|
370
|
+
acquired: false,
|
|
371
|
+
reason: "held",
|
|
372
|
+
heldBy: { op: "_turns/box-steward", holder: "other-host:123:abcd", token: "t0", acquiredAt: "x", expiresAt: "y" },
|
|
373
|
+
});
|
|
374
|
+
const stderr = makeStderrSpy();
|
|
375
|
+
const exit = await runOp({ args: makeArgs({ path: "release" }), plugins: [], serializers: [] });
|
|
376
|
+
|
|
377
|
+
expect(exit).toBe(1);
|
|
378
|
+
const out = stderr.join("\n");
|
|
379
|
+
expect(out).toContain('steward "box-steward"');
|
|
380
|
+
expect(out).toContain("other-host:123:abcd");
|
|
381
|
+
expect(out).toContain("chant operator status --steward box-steward");
|
|
382
|
+
expect(releaseLeaseMock).not.toHaveBeenCalled();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("fountain form without --on fountain: refused, naming the redirect, no turn lease touched", async () => {
|
|
386
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map([localOp("release", [])]), errors: [] });
|
|
387
|
+
discoverStewardsMock.mockResolvedValueOnce(stewardOwning("release", "box-steward", "fountain"));
|
|
388
|
+
const stderr = makeStderrSpy();
|
|
389
|
+
const exit = await runOp({ args: makeArgs({ path: "release" }), plugins: [], serializers: [] });
|
|
390
|
+
|
|
391
|
+
expect(exit).toBe(1);
|
|
392
|
+
const out = stderr.join("\n");
|
|
393
|
+
expect(out).toContain('steward "box-steward"');
|
|
394
|
+
expect(out).toContain("chant run release --on fountain");
|
|
395
|
+
expect(acquireStewardTurnMock).not.toHaveBeenCalled();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("fountain form with --on fountain: the local turn-lease check is skipped and the fountain runtime runs it", async () => {
|
|
399
|
+
const runtime = {
|
|
400
|
+
name: "fountain",
|
|
401
|
+
start: vi.fn(async (op: { name: string }) => ({
|
|
402
|
+
op: op.name,
|
|
403
|
+
runId: "f-1",
|
|
404
|
+
result: async () => ({ op: op.name, runId: "f-1", state: "completed" as const, startedAt: "t", endedAt: "t" }),
|
|
405
|
+
})),
|
|
406
|
+
status: vi.fn(), log: vi.fn(), list: vi.fn(), cancel: vi.fn(),
|
|
407
|
+
};
|
|
408
|
+
discoverOpsMock.mockResolvedValue({ ops: new Map([localOp("release", [])]), errors: [] });
|
|
409
|
+
discoverStewardsMock.mockResolvedValueOnce(stewardOwning("release", "box-steward", "fountain"));
|
|
410
|
+
loadChantConfigMock.mockResolvedValue({ config: { lexicons: ["fountain"] } });
|
|
411
|
+
makeStdoutSpy();
|
|
412
|
+
const exit = await runOp({
|
|
413
|
+
args: makeArgs({ path: "release", on: "fountain" }),
|
|
414
|
+
plugins: [{ name: "fountain", opRuntime: runtime } as never], serializers: [],
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
expect(exit).toBe(0);
|
|
418
|
+
expect(runtime.start).toHaveBeenCalledTimes(1);
|
|
419
|
+
expect(acquireStewardTurnMock).not.toHaveBeenCalled();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("local form, the turn lease's own acquire throws (a stale .lock): refused as a lease error, not an uncaught exception, and nothing runs", async () => {
|
|
423
|
+
discoverOpsMock.mockResolvedValue({
|
|
424
|
+
ops: new Map([localOp("release", [{ kind: "activity", fn: "shellCmd", args: { cmd: "true" } }])]),
|
|
425
|
+
errors: [],
|
|
426
|
+
});
|
|
427
|
+
discoverStewardsMock.mockResolvedValueOnce(stewardOwning("release", "box-steward"));
|
|
428
|
+
acquireStewardTurnMock.mockRejectedValue(new Error("cannot lock ref 'refs/chant/lease/_turns/box-steward': File exists"));
|
|
429
|
+
const stderr = makeStderrSpy();
|
|
430
|
+
|
|
431
|
+
const exit = await runOp({ args: makeArgs({ path: "release" }), plugins: [], serializers: [] });
|
|
432
|
+
|
|
433
|
+
expect(exit).toBe(1);
|
|
434
|
+
const out = stderr.join("\n");
|
|
435
|
+
expect(out).toContain('steward "box-steward"');
|
|
436
|
+
expect(out).toContain("cannot lock ref");
|
|
437
|
+
expect(releaseLeaseMock).not.toHaveBeenCalled();
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
272
441
|
/**
|
|
273
442
|
* chant #2243 — a gated run's exit code is the one thing `--gated-exit`
|
|
274
443
|
* remaps, and the gate is reported where CI can see it without opening a log.
|
|
@@ -354,7 +523,9 @@ describe("runOp: --gated-exit (#2243)", () => {
|
|
|
354
523
|
|
|
355
524
|
test("a run that fails for any other reason is still red under the flag", async () => {
|
|
356
525
|
discoverOpsMock.mockResolvedValue({
|
|
357
|
-
|
|
526
|
+
// atMostOnce: one attempt. The default profile retries twice with 5s and
|
|
527
|
+
// 10s backoff, which put this test at 15s for waits it does not test (#2817).
|
|
528
|
+
ops: new Map([localOp("broken", [{ kind: "activity", fn: "shellCmd", profile: "atMostOnce", args: { cmd: "exit 7" } }])]),
|
|
358
529
|
errors: [],
|
|
359
530
|
});
|
|
360
531
|
const file = summaryFile();
|
package/src/cli/handlers/run.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { lexiconNames } from "../../lexicon-module";
|
|
|
2
2
|
import { resolve, dirname } from "node:path";
|
|
3
3
|
import { writeFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
4
4
|
import { loadChantConfig, resolveAutoReleaseDisabled, type ChantConfig } from "../../config";
|
|
5
|
-
import { discoverOps } from "../../op/discover";
|
|
5
|
+
import { discoverOps, discoverStewards } from "../../op/discover";
|
|
6
|
+
import { stewardWorkHolder } from "../../op/work-lease-run";
|
|
6
7
|
import type { OpConfig } from "../../op/types";
|
|
7
8
|
import { loadActivities, loadProfiles } from "../../op/activity-registry";
|
|
8
9
|
import { runOpLocally, findPolicyGateStep, OpRunFailure, type StepRecord } from "../../op/local-executor";
|
|
@@ -10,12 +11,16 @@ import { approveCommand, describeGateMismatch } from "../../op/gate";
|
|
|
10
11
|
import { summaryLedgerPrefix, writeGatedRunSummary, type GatedRunSummary } from "../../op/gate-summary";
|
|
11
12
|
import { createLocalOpRuntime } from "../../op/runtimes/local";
|
|
12
13
|
import type { OpRuntimeProvider, OpRunStatus } from "../../op/runtime";
|
|
13
|
-
import { renderHuman, renderJson } from "../../op/local-output";
|
|
14
|
+
import { pointAnswerCommand, renderHuman, renderJson } from "../../op/local-output";
|
|
14
15
|
import { loadPlugins } from "../plugins";
|
|
15
16
|
import { recordGateApproval } from "./operator";
|
|
16
17
|
import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format";
|
|
17
18
|
import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
|
|
18
19
|
import type { CommandContext } from "../registry";
|
|
20
|
+
import { stewardFormFor, stewardTurnLeaseName, DEFAULT_STEWARD_ENV, type StewardDeclaration } from "../../op/steward";
|
|
21
|
+
import { acquireStewardTurn, STEWARD_TURN_WAIT_MS } from "../../op/operator";
|
|
22
|
+
import { releaseLease, currentHolderId, type AcquireLeaseResult } from "../../lifecycle/lease";
|
|
23
|
+
import { StaleLockError } from "../../lifecycle/git";
|
|
19
24
|
import { renderDriverHuman, renderDriverJson } from "../../components/driver-output";
|
|
20
25
|
import { ndjsonProgressSink } from "../../components/run-progress";
|
|
21
26
|
import { maybeRecordAutoRelease } from "../../components/auto-release";
|
|
@@ -526,6 +531,94 @@ function refusesPolicyGateUnderSandbox(ctx: CommandContext, config: OpConfig, op
|
|
|
526
531
|
return true;
|
|
527
532
|
}
|
|
528
533
|
|
|
534
|
+
/** What `stewardTurnGate` decided before `chant run <op>` is allowed to start. */
|
|
535
|
+
type StewardTurnGate =
|
|
536
|
+
| { ok: true; release?: () => Promise<void> }
|
|
537
|
+
| { ok: false; message: string; hint: string };
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* `chant run <op>` for an Op a declared steward lists is one of that
|
|
541
|
+
* steward's turns, not a run beside it (#2750, follow-up to #2731).
|
|
542
|
+
*
|
|
543
|
+
* On fountain, the run belongs on the steward's thread: without `--on
|
|
544
|
+
* fountain` this refuses outright, naming the command that posts it there
|
|
545
|
+
* instead (the fountain runtime's own turn already refuses a busy teammate,
|
|
546
|
+
* unretried — see chant-fountain-ops).
|
|
547
|
+
*
|
|
548
|
+
* In the local form, the run takes the steward's turn lease
|
|
549
|
+
* (`../../op/operator.ts`'s `acquireStewardTurn`) — the same lease a round's
|
|
550
|
+
* scheduled tick holds for the length of its own run. Held already, this
|
|
551
|
+
* waits up to {@link STEWARD_TURN_WAIT_MS} (long enough to ride out a race
|
|
552
|
+
* with a turn that's just ending) and then refuses, naming the steward and
|
|
553
|
+
* its holder. It never queues past that: a round's own leases skip and
|
|
554
|
+
* report rather than wait (`../../op/operator.ts`'s module doc), and a
|
|
555
|
+
* foreground CLI blocked with no visibility for however long a scheduled
|
|
556
|
+
* turn takes would be a worse answer than a clear refusal with a retry in
|
|
557
|
+
* hand. Ctrl-C already means "abort my own run" here; it must not also mean
|
|
558
|
+
* "give up waiting for someone else's turn".
|
|
559
|
+
*
|
|
560
|
+
* `undefined` (via the caller checking `owner`) when no steward lists this
|
|
561
|
+
* op — the ordinary, untouched path.
|
|
562
|
+
*/
|
|
563
|
+
async function stewardTurnGate(opName: string, ctx: CommandContext): Promise<StewardTurnGate | undefined> {
|
|
564
|
+
const { stewards } = await discoverStewards().catch(() => ({ stewards: new Map<string, { declaration: StewardDeclaration }>() }));
|
|
565
|
+
let owner: StewardDeclaration | undefined;
|
|
566
|
+
for (const { declaration } of stewards.values()) {
|
|
567
|
+
if (declaration.ops.some((op) => op.name === opName)) {
|
|
568
|
+
owner = declaration;
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (!owner) return undefined;
|
|
573
|
+
|
|
574
|
+
const env = ctx.args.env ?? DEFAULT_STEWARD_ENV;
|
|
575
|
+
const form = stewardFormFor(owner, env);
|
|
576
|
+
|
|
577
|
+
if (form === "fountain") {
|
|
578
|
+
if (ctx.args.on === "fountain") return { ok: true };
|
|
579
|
+
return {
|
|
580
|
+
ok: false,
|
|
581
|
+
message: `Op "${opName}" is one of steward "${owner.name}"'s Ops, which runs on fountain in environment "${env}"`,
|
|
582
|
+
hint:
|
|
583
|
+
`Run \`chant run ${opName} --on fountain\` so it posts to "${owner.name}"'s thread. ` +
|
|
584
|
+
`A plain \`chant run ${opName}\` would run it outside that thread, beside the steward instead of one of its turns.`,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const holder = currentHolderId();
|
|
589
|
+
let turn: AcquireLeaseResult;
|
|
590
|
+
try {
|
|
591
|
+
turn = await acquireStewardTurn(owner.name, holder, { waitMs: STEWARD_TURN_WAIT_MS });
|
|
592
|
+
} catch (err) {
|
|
593
|
+
// Not turn contention — the acquire attempt itself failed (most likely a
|
|
594
|
+
// StaleLockError, a `.lock` left behind by a killed operator process).
|
|
595
|
+
// Report it as a refusal rather than letting it propagate uncaught out
|
|
596
|
+
// of `runOpOnRuntime`, before that function's own try/catch even starts.
|
|
597
|
+
return {
|
|
598
|
+
ok: false,
|
|
599
|
+
message: `Op "${opName}" is one of steward "${owner.name}"'s turns, and its lease could not be read`,
|
|
600
|
+
hint:
|
|
601
|
+
`${err instanceof StaleLockError ? err.message : err instanceof Error ? err.message : String(err)} ` +
|
|
602
|
+
`This can happen when an operator process was killed mid-write; check refs/chant/lease/_turns/${owner.name} for a stale lock.`,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
if (!turn.acquired) {
|
|
606
|
+
const heldBy = turn.heldBy?.holder;
|
|
607
|
+
return {
|
|
608
|
+
ok: false,
|
|
609
|
+
message:
|
|
610
|
+
`Op "${opName}" is one of steward "${owner.name}"'s turns, and a turn is in progress` +
|
|
611
|
+
(heldBy ? ` (held by ${heldBy})` : ""),
|
|
612
|
+
hint:
|
|
613
|
+
`Wait for the turn to finish and re-run \`chant run ${opName}\`, or check \`chant operator status --steward ${owner.name}\`. ` +
|
|
614
|
+
`If it looks stuck, stop the operator process holding it (Ctrl-C, or kill it) — its lease also expires on its own.`,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
const lease = turn.lease!;
|
|
618
|
+
const stewardName = owner.name;
|
|
619
|
+
return { ok: true, release: async () => { await releaseLease(stewardTurnLeaseName(stewardName), holder, lease.token).catch(() => false); } };
|
|
620
|
+
}
|
|
621
|
+
|
|
529
622
|
export async function runOp(ctx: CommandContext): Promise<number> {
|
|
530
623
|
if (ctx.args.generate) {
|
|
531
624
|
if (ctx.args.components) {
|
|
@@ -863,8 +956,26 @@ export async function runOpOnRuntime(ctx: CommandContext): Promise<number> {
|
|
|
863
956
|
// Pre-flight: --sandbox cannot cover a policyGate step (#2003).
|
|
864
957
|
if (refusesPolicyGateUnderSandbox(ctx, config, opName)) return 1;
|
|
865
958
|
|
|
959
|
+
// A hand run of an Op a declared steward lists is one of that steward's
|
|
960
|
+
// turns, not a run beside it (#2750). `undefined` when no steward lists
|
|
961
|
+
// this op at all — the ordinary path.
|
|
962
|
+
const stewardGate = await stewardTurnGate(opName, ctx);
|
|
963
|
+
if (stewardGate && !stewardGate.ok) {
|
|
964
|
+
console.error(formatError({ message: stewardGate.message, hint: stewardGate.hint }));
|
|
965
|
+
return 1;
|
|
966
|
+
}
|
|
967
|
+
|
|
866
968
|
const runtime = await resolveOpRuntime(ctx);
|
|
867
|
-
if (!runtime)
|
|
969
|
+
if (!runtime) {
|
|
970
|
+
await stewardGate?.release?.();
|
|
971
|
+
return 1;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// The work lease (#2748). `--work` names the item an Op with a work lease
|
|
975
|
+
// runs under; it is taken where the run executes, which for a hosted
|
|
976
|
+
// runtime is not this process.
|
|
977
|
+
const work = await resolveRunWork(ctx, config, runtime.name);
|
|
978
|
+
if (work === null) return 1;
|
|
868
979
|
|
|
869
980
|
// `--progress-json` streams one NDJSON StepRecord per settled step, fed by
|
|
870
981
|
// whatever the runtime reports through `progress`.
|
|
@@ -885,6 +996,7 @@ export async function runOpOnRuntime(ctx: CommandContext): Promise<number> {
|
|
|
885
996
|
...(ctx.args.profile !== undefined ? { profile: ctx.args.profile } : {}),
|
|
886
997
|
progress,
|
|
887
998
|
signal: controller.signal,
|
|
999
|
+
...(work ? { work } : {}),
|
|
888
1000
|
});
|
|
889
1001
|
const status = await handle.result();
|
|
890
1002
|
|
|
@@ -932,6 +1044,18 @@ export async function runOpOnRuntime(ctx: CommandContext): Promise<number> {
|
|
|
932
1044
|
}
|
|
933
1045
|
return gatedExit;
|
|
934
1046
|
}
|
|
1047
|
+
// A run waiting on an open decision point (#2749) is waiting on a person,
|
|
1048
|
+
// like a gated one, and exits with the same code. Its answer comes through
|
|
1049
|
+
// hud or `points answer`, never a `chant approve`.
|
|
1050
|
+
if (status.state === "waiting") {
|
|
1051
|
+
if (!status.result && status.point) {
|
|
1052
|
+
console.error(formatWarning({
|
|
1053
|
+
message: `Op "${opName}" is waiting on decision point "${status.point.point}" (${status.point.id})`,
|
|
1054
|
+
hint: `A person answers it with: ${pointAnswerCommand(status.point.id)}`,
|
|
1055
|
+
}));
|
|
1056
|
+
}
|
|
1057
|
+
return gatedExit;
|
|
1058
|
+
}
|
|
935
1059
|
return status.state === "completed" ? 0 : 1;
|
|
936
1060
|
} catch (err) {
|
|
937
1061
|
if (err instanceof OpRunFailure) {
|
|
@@ -942,7 +1066,54 @@ export async function runOpOnRuntime(ctx: CommandContext): Promise<number> {
|
|
|
942
1066
|
return 1;
|
|
943
1067
|
} finally {
|
|
944
1068
|
process.removeListener("SIGINT", onSigint);
|
|
1069
|
+
// The turn is over, win or lose — free it so the steward's next round,
|
|
1070
|
+
// or the next hand run, doesn't wait out this run's own TTL (#2750).
|
|
1071
|
+
await stewardGate?.release?.();
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* The work item and holder a `chant run` passes to the runtime (#2748), or
|
|
1077
|
+
* `null` after printing why the run is refused. The holder is `--holder`, or,
|
|
1078
|
+
* for an Op a steward lists, `<steward>/<op>@<process>` so `workspace status`
|
|
1079
|
+
* shows the lease beside the steward's Ops, or this process's id.
|
|
1080
|
+
*/
|
|
1081
|
+
async function resolveRunWork(
|
|
1082
|
+
ctx: CommandContext,
|
|
1083
|
+
config: OpConfig,
|
|
1084
|
+
runtime: string,
|
|
1085
|
+
): Promise<{ item?: string; holder?: string } | undefined | null> {
|
|
1086
|
+
const item = ctx.args.work;
|
|
1087
|
+
if (!config.workLease) {
|
|
1088
|
+
if (item === undefined) return undefined;
|
|
1089
|
+
console.error(formatError({
|
|
1090
|
+
message: `--work ${item}: Op "${config.name}" declares no work lease`,
|
|
1091
|
+
hint: "Declare workLease on the Op to run it under a work item's lease.",
|
|
1092
|
+
}));
|
|
1093
|
+
return null;
|
|
1094
|
+
}
|
|
1095
|
+
if (runtime !== "local" && (item !== undefined || ctx.args.holder !== undefined)) {
|
|
1096
|
+
console.error(formatError({
|
|
1097
|
+
message: `--work and --holder run on the local runtime; "${runtime}" takes the work lease where it executes the run`,
|
|
1098
|
+
hint: "Omit --on, or name the item in the Op's workLease.",
|
|
1099
|
+
}));
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
let holder = ctx.args.holder;
|
|
1103
|
+
if (holder === undefined) {
|
|
1104
|
+
try {
|
|
1105
|
+
const { stewards } = await discoverStewards();
|
|
1106
|
+
for (const { declaration } of stewards.values()) {
|
|
1107
|
+
if (declaration.ops.some((op) => op.name === config.name)) {
|
|
1108
|
+
holder = stewardWorkHolder(declaration.name, config.name);
|
|
1109
|
+
break;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
} catch {
|
|
1113
|
+
// No steward can be read: the run holds the lease as this process.
|
|
1114
|
+
}
|
|
945
1115
|
}
|
|
1116
|
+
return { ...(item !== undefined ? { item } : {}), ...(holder !== undefined ? { holder } : {}) };
|
|
946
1117
|
}
|
|
947
1118
|
|
|
948
1119
|
// ── fallback ────────────────────────────────────────────────────────────────���─
|
package/src/cli/main.test.ts
CHANGED
|
@@ -276,6 +276,18 @@ describe("parseArgs", () => {
|
|
|
276
276
|
expect(result.actor).toBe("alice");
|
|
277
277
|
});
|
|
278
278
|
|
|
279
|
+
test("parses --release-plan for components release, distinct from --plan (#2300)'s approval-digest flag (ws-055, #2733)", () => {
|
|
280
|
+
const result = parseArgs([
|
|
281
|
+
"components", "release", "prod",
|
|
282
|
+
"--component", "search-service",
|
|
283
|
+
"--release-plan", "plan.json",
|
|
284
|
+
"--actor", "alice",
|
|
285
|
+
]);
|
|
286
|
+
expect(result.releasePlanFile).toBe("plan.json");
|
|
287
|
+
expect(result.digest).toBeUndefined();
|
|
288
|
+
expect(result.plan).toBeUndefined();
|
|
289
|
+
});
|
|
290
|
+
|
|
279
291
|
test("--digest repeats for components promote, and --digest-file is parsed (#2602)", () => {
|
|
280
292
|
const promote = parseArgs([
|
|
281
293
|
"components", "promote", "--from", "staging", "--to", "prod",
|
|
@@ -715,6 +727,16 @@ describe("workspace graph --intent (#2651)", () => {
|
|
|
715
727
|
});
|
|
716
728
|
});
|
|
717
729
|
|
|
730
|
+
describe("workspace check --changes (#2773)", () => {
|
|
731
|
+
test("parses the range, the work item and the severity", () => {
|
|
732
|
+
const args = parseArgs(["workspace", "check", "--changes", "main..HEAD", "--work", "W-001", "--severity", "fail", "--json"]);
|
|
733
|
+
expect(args).toMatchObject({ command: "workspace", path: "check", changes: "main..HEAD", work: "W-001", severity: "fail", json: true });
|
|
734
|
+
expect(() => parseArgs(["workspace", "check", "--changes"])).toThrow(/--changes needs a range/);
|
|
735
|
+
expect(() => parseArgs(["workspace", "check", "--changes", "main", "--severity"])).toThrow(/--severity needs off, warn or fail/);
|
|
736
|
+
expect(resolveCommand(args, commandRegistry)?.def.name).toBe("workspace check");
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
|
|
718
740
|
describe("workspace init and ls (#2534)", () => {
|
|
719
741
|
test("resolve to their commands in the one workspace group, with the directory as extra positional", () => {
|
|
720
742
|
const ls = parseArgs(["workspace", "ls", "examples", "--at", "HEAD", "--json"]);
|
|
@@ -734,13 +756,28 @@ describe("workspace init and ls (#2534)", () => {
|
|
|
734
756
|
"workspace lint",
|
|
735
757
|
"workspace ls",
|
|
736
758
|
"workspace member-run",
|
|
759
|
+
"workspace points",
|
|
737
760
|
"workspace records",
|
|
738
761
|
"workspace status",
|
|
739
762
|
"workspace upgrade",
|
|
740
763
|
"workspace verify",
|
|
764
|
+
"workspace work",
|
|
741
765
|
]);
|
|
742
766
|
});
|
|
743
767
|
|
|
768
|
+
test("workspace work takes its verb, id and lease flags (#2732)", () => {
|
|
769
|
+
const claim = parseArgs(["workspace", "work", "claim", "W-001", "--holder", "a", "--ttl", "60", "--json"]);
|
|
770
|
+
expect(claim).toMatchObject({ command: "workspace", path: "work", extraPositional: "claim", extraPositional2: "W-001", holder: "a", ttl: "60", json: true });
|
|
771
|
+
expect(resolveCommand(claim, commandRegistry)?.def.name).toBe("workspace work");
|
|
772
|
+
expect(parseArgs(["workspace", "work", "release", "W-001", "--holder=a", "--token", "t", "--outcome", "done"])).toMatchObject({ holder: "a", token: "t", outcome: "done" });
|
|
773
|
+
expect(() => parseArgs(["workspace", "work", "claim", "W-001", "--holder"])).toThrow(/--holder needs a value/);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
test("run takes --work <id> for an Op with a work lease (#2748)", () => {
|
|
777
|
+
expect(parseArgs(["run", "dispatch", "--work", "W-001", "--holder", "box/dispatch@h"])).toMatchObject({ command: "run", path: "dispatch", work: "W-001", holder: "box/dispatch@h" });
|
|
778
|
+
expect(() => parseArgs(["run", "dispatch", "--work"])).toThrow(/--work needs a work item id/);
|
|
779
|
+
});
|
|
780
|
+
|
|
744
781
|
test("load their modules only when they run", () => {
|
|
745
782
|
const source = readFileSync(join(import.meta.dirname, "main.ts"), "utf-8");
|
|
746
783
|
expect(source).toMatch(/await import\("\.\.\/workspace\/init"\)/);
|
|
@@ -748,6 +785,19 @@ describe("workspace init and ls (#2534)", () => {
|
|
|
748
785
|
});
|
|
749
786
|
});
|
|
750
787
|
|
|
788
|
+
describe("workspace points (ws-058, #2739)", () => {
|
|
789
|
+
test("ask and answer take their flags, and --by repeats", () => {
|
|
790
|
+
const ask = parseArgs(["workspace", "points", "ask", "slice-tier", "--inputs", "in.json", "--response", "r.json", "--subject", "W-001", "--dry-run"]);
|
|
791
|
+
expect(ask).toMatchObject({ path: "points", extraPositional: "ask", extraPositional2: "slice-tier", inputs: "in.json", response: "r.json", subject: "W-001", dryRun: true });
|
|
792
|
+
expect(resolveCommand(ask, commandRegistry)?.def.name).toBe("workspace points");
|
|
793
|
+
expect(parseArgs(["workspace", "points", "ask", "p", "--inputs", "-"]).inputs).toBe("-");
|
|
794
|
+
const answer = parseArgs(["workspace", "points", "answer", "slice-tier-0123456789ab", "--answer", "medium", "--by", "alice", "--by", "bob"]);
|
|
795
|
+
expect(answer).toMatchObject({ extraPositional: "answer", answer: "medium", bys: ["alice", "bob"] });
|
|
796
|
+
expect(parseArgs(["workspace", "points", "--open", "--json"])).toMatchObject({ open: true, json: true });
|
|
797
|
+
expect(() => parseArgs(["workspace", "points", "ask", "p", "--response", "--json"])).toThrow(/--response needs a JSON file/);
|
|
798
|
+
});
|
|
799
|
+
});
|
|
800
|
+
|
|
751
801
|
describe("workspace status (#2544)", () => {
|
|
752
802
|
test("takes the environment as extra positional, a directory after it, and --compare-to", () => {
|
|
753
803
|
const status = parseArgs(["workspace", "status", "staging", "apps", "--compare-to", "prod", "--json"]);
|