@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/op/local-executor.ts
CHANGED
|
@@ -25,13 +25,19 @@ import { resolveActivity, type ActivityFn, type ActivityProfile } from "./activi
|
|
|
25
25
|
import type { ReceiptReadResult } from "./receipt-store";
|
|
26
26
|
import { isStepOutputRef } from "./step-output-ref";
|
|
27
27
|
import { parseDuration } from "./duration";
|
|
28
|
+
import { stepTimeoutProblem } from "./activity-profiles";
|
|
28
29
|
import { describeGateMismatch, evaluateGate, gitGateLedgerPort, type GateCheck, type GateLedgerPort } from "./gate";
|
|
29
30
|
import { gateName } from "./gate-name";
|
|
30
31
|
import type { ResolvedGateApproval } from "./gate-approval";
|
|
31
32
|
import type { PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
33
|
+
import { isPointWait, type WaitingPoint } from "./steward-points";
|
|
34
|
+
import { isGateWait } from "./gate-wait";
|
|
35
|
+
import { currentStewardTurn, enterStewardTurn } from "./steward-turn";
|
|
32
36
|
import { appendRunRecord, buildRunRecord } from "../lifecycle/run-ledger";
|
|
33
37
|
import type { OpRunRecord } from "./runtime";
|
|
34
38
|
import { randomUUID } from "node:crypto";
|
|
39
|
+
import { RunWorkLease, LEASE_LOST, workLeaseProblems, workLeaseNeedsRunItem, type WorkLeaseRunResult } from "./work-lease-run";
|
|
40
|
+
import { WORK_LEASE_STEP_ID } from "./types";
|
|
35
41
|
|
|
36
42
|
export { parseDuration } from "./duration";
|
|
37
43
|
|
|
@@ -76,6 +82,18 @@ export interface StepRecord {
|
|
|
76
82
|
* was applied.
|
|
77
83
|
*/
|
|
78
84
|
refusal?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Set on an activity step that asked a decision point still open (#2749):
|
|
87
|
+
* the question the run now waits on. The step's status is `skipped` and the
|
|
88
|
+
* run's is `waiting`: nothing broke, and a person answers the question.
|
|
89
|
+
*/
|
|
90
|
+
point?: WaitingPoint;
|
|
91
|
+
/**
|
|
92
|
+
* Set on an activity step whose command stopped at a gate of its own
|
|
93
|
+
* (#2779): the op the gate is recorded under and its name, what `chant
|
|
94
|
+
* approve` takes. The step's status is `skipped` and the run's is `gated`.
|
|
95
|
+
*/
|
|
96
|
+
gate?: { op: string; gate: string };
|
|
79
97
|
}
|
|
80
98
|
|
|
81
99
|
export interface OpRunResult {
|
|
@@ -89,11 +107,17 @@ export interface OpRunResult {
|
|
|
89
107
|
*
|
|
90
108
|
* Written to the run ledger as the record's own `status` (#2118).
|
|
91
109
|
*/
|
|
92
|
-
status: "ok" | "fail" | "gated";
|
|
110
|
+
status: "ok" | "fail" | "gated" | "waiting";
|
|
93
111
|
/** ISO-8601 start of this run. */
|
|
94
112
|
startedAt: string;
|
|
95
113
|
/** Present when `status === "gated"`: the pending fact the run ended on. */
|
|
96
114
|
gate?: PendingGateRecord;
|
|
115
|
+
/**
|
|
116
|
+
* Present when `status === "waiting"` (#2749): the open decision point the
|
|
117
|
+
* run stopped on. Like a gate, it is a fact and not a wait: a person answers
|
|
118
|
+
* the question, and the next run reads the answer.
|
|
119
|
+
*/
|
|
120
|
+
point?: WaitingPoint;
|
|
97
121
|
/**
|
|
98
122
|
* Present when `status === "gated"` and this run's own append tried to push:
|
|
99
123
|
* whether it reached the remote (#2310). Absent when the run stopped on a
|
|
@@ -110,6 +134,12 @@ export interface OpRunResult {
|
|
|
110
134
|
* document.
|
|
111
135
|
*/
|
|
112
136
|
record: OpRunRecord;
|
|
137
|
+
/**
|
|
138
|
+
* How the run's work lease went, for an Op that declares `workLease`
|
|
139
|
+
* (#2748): the item it claimed, or why it claimed none, whether the lease
|
|
140
|
+
* was released and with which outcome, or why it was lost.
|
|
141
|
+
*/
|
|
142
|
+
workLease?: WorkLeaseRunResult;
|
|
113
143
|
}
|
|
114
144
|
|
|
115
145
|
// ── Errors ────────────────────────────────────────────────────────────────��─
|
|
@@ -165,6 +195,35 @@ class GateStop extends Error {
|
|
|
165
195
|
}
|
|
166
196
|
}
|
|
167
197
|
|
|
198
|
+
/** Internal: a step asked a decision point that is still open (#2749). Not a failure — no compensation follows it. */
|
|
199
|
+
class PointStop extends Error {
|
|
200
|
+
constructor(
|
|
201
|
+
public readonly records: StepRecord[],
|
|
202
|
+
public readonly question: WaitingPoint,
|
|
203
|
+
public readonly phase: string,
|
|
204
|
+
) {
|
|
205
|
+
super(`waiting on decision point ${question.id}`);
|
|
206
|
+
this.name = "PointStop";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Internal: a step boundary found the run can't go on under its work lease
|
|
212
|
+
* (#2748). `lost` false is a claim that found nothing to claim: not a
|
|
213
|
+
* failure, the run ends `ok` with the rest skipped. `lost` true is a lease
|
|
214
|
+
* that was lost: the run fails with `lease-lost`.
|
|
215
|
+
*/
|
|
216
|
+
class WorkStop extends Error {
|
|
217
|
+
constructor(
|
|
218
|
+
public readonly records: StepRecord[],
|
|
219
|
+
public readonly phase: string,
|
|
220
|
+
public readonly lost: boolean,
|
|
221
|
+
) {
|
|
222
|
+
super(lost ? LEASE_LOST : "nothing to claim");
|
|
223
|
+
this.name = "WorkStop";
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
168
227
|
// ── Helpers ───────────────────────────────────────────────────────────────��─
|
|
169
228
|
|
|
170
229
|
const DEFAULT_PROFILE = "fastIdempotent";
|
|
@@ -287,6 +346,8 @@ async function callWithTimeout(
|
|
|
287
346
|
interface RanStep {
|
|
288
347
|
record: StepRecord;
|
|
289
348
|
result?: unknown;
|
|
349
|
+
/** The pending fact a step's command stopped at (#2779), when it threw {@link GateWait}. */
|
|
350
|
+
pending?: PendingGateRecord;
|
|
290
351
|
}
|
|
291
352
|
|
|
292
353
|
/**
|
|
@@ -316,9 +377,16 @@ async function runStep(
|
|
|
316
377
|
}
|
|
317
378
|
|
|
318
379
|
const profile = profiles[step.profile ?? DEFAULT_PROFILE] ?? {};
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
380
|
+
// A step's own timeout (#2787) replaces the profile's and keeps its retries.
|
|
381
|
+
if (step.timeout !== undefined) {
|
|
382
|
+
const problem = stepTimeoutProblem(step.timeout);
|
|
383
|
+
if (problem) return { record: { ...base, status: "fail", durationMs: 0, error: problem } };
|
|
384
|
+
}
|
|
385
|
+
const timeoutMs = step.timeout !== undefined
|
|
386
|
+
? parseDuration(step.timeout)
|
|
387
|
+
: profile.timeout
|
|
388
|
+
? parseDuration(profile.timeout)
|
|
389
|
+
: FALLBACK_TIMEOUT_MS;
|
|
322
390
|
const maxAttempts =
|
|
323
391
|
profile.retry?.maximumAttempts && profile.retry.maximumAttempts > 0
|
|
324
392
|
? profile.retry.maximumAttempts
|
|
@@ -343,6 +411,17 @@ async function runStep(
|
|
|
343
411
|
}
|
|
344
412
|
return { record, result };
|
|
345
413
|
} catch (err) {
|
|
414
|
+
// An open decision point (#2749) is not a failure and not worth a retry:
|
|
415
|
+
// the answer comes from a person, on a later run.
|
|
416
|
+
if (isPointWait(err)) {
|
|
417
|
+
return { record: { ...base, status: "skipped", durationMs: Date.now() - start, point: err.question } };
|
|
418
|
+
}
|
|
419
|
+
// Nor is a command that stopped at a gate of its own (#2779): the run
|
|
420
|
+
// ends `gated` there, and the next run asks the command again.
|
|
421
|
+
if (isGateWait(err)) {
|
|
422
|
+
const gate = { op: err.pending.op, gate: err.pending.gate };
|
|
423
|
+
return { record: { ...base, status: "skipped", durationMs: Date.now() - start, gate }, pending: err.pending };
|
|
424
|
+
}
|
|
346
425
|
lastErr = err;
|
|
347
426
|
// Stop retrying on abort (Ctrl-C / timeout cascade) or a non-retryable error.
|
|
348
427
|
const fatal =
|
|
@@ -391,6 +470,68 @@ interface GateContext {
|
|
|
391
470
|
runId?: string;
|
|
392
471
|
/** Called once per settled step, in production order (#2121) — what `--progress-json` streams from. */
|
|
393
472
|
onRecord?: (record: StepRecord) => void;
|
|
473
|
+
/** The run's hold on its work item's lease (#2748). Absent in compensation phases, which run whatever the lease did. */
|
|
474
|
+
work?: RunWorkLease;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* The work lease at a step boundary (#2748): claim it when it is due, and
|
|
479
|
+
* say whether the run can go on. The claim's record is pushed onto `sink`.
|
|
480
|
+
* Returns `undefined` to go on, `"unclaimed"` when nothing could be claimed,
|
|
481
|
+
* and `"lost"` when the lease was lost.
|
|
482
|
+
*/
|
|
483
|
+
async function workBoundary(
|
|
484
|
+
phaseName: string,
|
|
485
|
+
ctx: GateContext,
|
|
486
|
+
resultsById: Map<string, unknown>,
|
|
487
|
+
sink: StepRecord[],
|
|
488
|
+
): Promise<"unclaimed" | "lost" | undefined> {
|
|
489
|
+
const work = ctx.work;
|
|
490
|
+
if (!work) return undefined;
|
|
491
|
+
if (work.due(resultsById)) {
|
|
492
|
+
const start = Date.now();
|
|
493
|
+
let claimed: Awaited<ReturnType<RunWorkLease["claim"]>>;
|
|
494
|
+
try {
|
|
495
|
+
claimed = await work.claim(resultsById);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
pushRecord(sink, ctx, {
|
|
498
|
+
phase: phaseName,
|
|
499
|
+
fn: "workLease:claim",
|
|
500
|
+
args: {},
|
|
501
|
+
status: "fail",
|
|
502
|
+
durationMs: Date.now() - start,
|
|
503
|
+
error: errMessage(err),
|
|
504
|
+
});
|
|
505
|
+
throw new PhaseFailure(sink);
|
|
506
|
+
}
|
|
507
|
+
if (claimed.kind === "unclaimed") {
|
|
508
|
+
pushRecord(sink, ctx, {
|
|
509
|
+
phase: phaseName,
|
|
510
|
+
fn: "workLease:claim",
|
|
511
|
+
args: { items: claimed.tried },
|
|
512
|
+
status: "skipped",
|
|
513
|
+
durationMs: Date.now() - start,
|
|
514
|
+
refusal: claimed.refusal,
|
|
515
|
+
outcome: { name: "WorkItem", value: null },
|
|
516
|
+
outcomes: [{ name: "WorkItem", value: null }],
|
|
517
|
+
});
|
|
518
|
+
return "unclaimed";
|
|
519
|
+
}
|
|
520
|
+
resultsById.set(WORK_LEASE_STEP_ID, claimed.lease);
|
|
521
|
+
pushRecord(sink, ctx, {
|
|
522
|
+
phase: phaseName,
|
|
523
|
+
fn: "workLease:claim",
|
|
524
|
+
args: { items: claimed.tried },
|
|
525
|
+
status: "ok",
|
|
526
|
+
durationMs: Date.now() - start,
|
|
527
|
+
outcome: { name: "WorkItem", value: claimed.lease.item },
|
|
528
|
+
outcomes: [
|
|
529
|
+
{ name: "WorkItem", value: claimed.lease.item },
|
|
530
|
+
{ name: "WorkLeaseToken", value: claimed.lease.token },
|
|
531
|
+
],
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
return work.lost !== undefined ? "lost" : undefined;
|
|
394
535
|
}
|
|
395
536
|
|
|
396
537
|
/** Collect records and hand each to the caller's progress sink in one move. */
|
|
@@ -553,6 +694,7 @@ async function runEffectStep(
|
|
|
553
694
|
pending?: PendingGateRecord;
|
|
554
695
|
pushed?: boolean;
|
|
555
696
|
pushWarning?: string;
|
|
697
|
+
point?: WaitingPoint;
|
|
556
698
|
}> {
|
|
557
699
|
const records: StepRecord[] = [];
|
|
558
700
|
|
|
@@ -618,6 +760,18 @@ async function runEffectStep(
|
|
|
618
760
|
}
|
|
619
761
|
const ran = await runStep(nested, phaseName, activities, profiles, resultsById, signal);
|
|
620
762
|
pushRecord(records, gates, ran.record);
|
|
763
|
+
if (ran.pending) {
|
|
764
|
+
// A command's own gate (#2779): the receipt is left untouched, as for a
|
|
765
|
+
// pending gate step.
|
|
766
|
+
skipRest(i + 1);
|
|
767
|
+
return { records, failed: false, pending: ran.pending };
|
|
768
|
+
}
|
|
769
|
+
if (ran.record.point) {
|
|
770
|
+
// Receipt left untouched, as for a pending gate: the next run re-proposes
|
|
771
|
+
// the effect and asks the point again, and finds the answer by then.
|
|
772
|
+
skipRest(i + 1);
|
|
773
|
+
return { records, failed: false, point: ran.record.point };
|
|
774
|
+
}
|
|
621
775
|
if (ran.record.status === "fail") {
|
|
622
776
|
// Receipt left untouched (stale) — the next run re-proposes the effect.
|
|
623
777
|
skipRest(i + 1);
|
|
@@ -666,6 +820,13 @@ async function runPhase(
|
|
|
666
820
|
// read of the ledger, not work, and starting activities that a pending
|
|
667
821
|
// gate is about to strand would defeat the point of stopping at it.
|
|
668
822
|
const gateRecords: StepRecord[] = [];
|
|
823
|
+
const stop = await workBoundary(phase.name, gates, resultsById, gateRecords);
|
|
824
|
+
if (stop) {
|
|
825
|
+
for (const skipped of phase.steps) {
|
|
826
|
+
pushRecord(gateRecords, gates, isGate(skipped) ? skippedRecord(phase.name, gateFn(skipped)) : skippedRecord(phase.name, (skipped as ActivityStep).fn, (skipped as ActivityStep).args));
|
|
827
|
+
}
|
|
828
|
+
throw new WorkStop(gateRecords, phase.name, stop === "lost");
|
|
829
|
+
}
|
|
669
830
|
for (const step of phase.steps.filter(isGate)) {
|
|
670
831
|
const { record, pending, pushed, pushWarning } = await runGateStep(step, phase.name, gates, resultsById);
|
|
671
832
|
pushRecord(gateRecords, gates, record);
|
|
@@ -685,12 +846,19 @@ async function runPhase(
|
|
|
685
846
|
}
|
|
686
847
|
}
|
|
687
848
|
const steps = phase.steps.filter(isActivity);
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
).map((r) => r.record);
|
|
849
|
+
const settled = await Promise.all(steps.map((s) => runStep(s, phase.name, activities, profiles, resultsById, signal)));
|
|
850
|
+
const ran = settled.map((r) => r.record);
|
|
691
851
|
for (const r of ran) gates.onRecord?.(r);
|
|
692
852
|
const records = gateRecords.concat(ran);
|
|
693
853
|
if (records.some((r) => r.status === "fail")) throw new PhaseFailure(records);
|
|
854
|
+
// A step whose command stopped at its own gate (#2779) stops the run at
|
|
855
|
+
// this phase, as a gate step would, once its siblings have finished.
|
|
856
|
+
const gated = settled.find((r) => r.pending)?.pending;
|
|
857
|
+
if (gated) throw new GateStop(records, gated, phase.name);
|
|
858
|
+
// The fan-out has already run, so the siblings of an open decision point
|
|
859
|
+
// finished; the run still stops at this phase (#2749).
|
|
860
|
+
const waiting = ran.find((r) => r.point);
|
|
861
|
+
if (waiting?.point) throw new PointStop(records, waiting.point, phase.name);
|
|
694
862
|
return records;
|
|
695
863
|
}
|
|
696
864
|
|
|
@@ -711,6 +879,13 @@ async function runPhase(
|
|
|
711
879
|
|
|
712
880
|
for (let i = 0; i < steps.length; i++) {
|
|
713
881
|
const step = steps[i];
|
|
882
|
+
// The work lease (#2748): claimed here when it is due, and checked before
|
|
883
|
+
// every step, so no step starts once the lease is lost.
|
|
884
|
+
const stop = await workBoundary(phase.name, gates, resultsById, records);
|
|
885
|
+
if (stop) {
|
|
886
|
+
skipRemaining(i);
|
|
887
|
+
throw new WorkStop(records, phase.name, stop === "lost");
|
|
888
|
+
}
|
|
714
889
|
if (isGate(step)) {
|
|
715
890
|
const { record, pending, pushed, pushWarning } = await runGateStep(step, phase.name, gates, resultsById);
|
|
716
891
|
pushRecord(records, gates, record);
|
|
@@ -727,10 +902,14 @@ async function runPhase(
|
|
|
727
902
|
continue;
|
|
728
903
|
}
|
|
729
904
|
if (isEffect(step)) {
|
|
730
|
-
const { records: effRecords, failed, pending, pushed, pushWarning } = await runEffectStep(
|
|
905
|
+
const { records: effRecords, failed, pending, pushed, pushWarning, point } = await runEffectStep(
|
|
731
906
|
step, phase.name, activities, profiles, resultsById, gates, signal,
|
|
732
907
|
);
|
|
733
908
|
records.push(...effRecords); // already emitted by runEffectStep
|
|
909
|
+
if (point) {
|
|
910
|
+
skipRemaining(i + 1);
|
|
911
|
+
throw new PointStop(records, point, phase.name);
|
|
912
|
+
}
|
|
734
913
|
if (pending) {
|
|
735
914
|
skipRemaining(i + 1);
|
|
736
915
|
throw new GateStop(records, pending, phase.name, pushed, pushWarning);
|
|
@@ -741,8 +920,19 @@ async function runPhase(
|
|
|
741
920
|
}
|
|
742
921
|
continue;
|
|
743
922
|
}
|
|
744
|
-
const { record } = await runStep(step, phase.name, activities, profiles, resultsById, signal);
|
|
923
|
+
const { record, pending } = await runStep(step, phase.name, activities, profiles, resultsById, signal);
|
|
745
924
|
pushRecord(records, gates, record);
|
|
925
|
+
if (pending) {
|
|
926
|
+
// The step's command stopped at its own gate (#2779): the run ends
|
|
927
|
+
// `gated` there, as at a pending gate step.
|
|
928
|
+
skipRemaining(i + 1);
|
|
929
|
+
throw new GateStop(records, pending, phase.name);
|
|
930
|
+
}
|
|
931
|
+
if (record.point) {
|
|
932
|
+
// An open decision point (#2749) ends the run as a pending gate does.
|
|
933
|
+
skipRemaining(i + 1);
|
|
934
|
+
throw new PointStop(records, record.point, phase.name);
|
|
935
|
+
}
|
|
746
936
|
if (record.status === "fail") {
|
|
747
937
|
// Mark the remaining steps in this phase as skipped, then abort.
|
|
748
938
|
skipRemaining(i + 1);
|
|
@@ -789,6 +979,17 @@ export interface RunOpOptions {
|
|
|
789
979
|
* never promoted into a run failure.
|
|
790
980
|
*/
|
|
791
981
|
onLedgerError?: (err: unknown) => void;
|
|
982
|
+
/** The steward whose turn this run is (#2749), recorded on its ledger record. */
|
|
983
|
+
steward?: string;
|
|
984
|
+
/**
|
|
985
|
+
* For an Op that declares `workLease` (#2748): the item this run is for
|
|
986
|
+
* (`chant run <op> --work <id>`), which takes the place of the Op's own, and
|
|
987
|
+
* who holds the lease (a steward's turn passes `stewardWorkHolder`).
|
|
988
|
+
* Defaults: the Op's item, and this process's holder id.
|
|
989
|
+
*/
|
|
990
|
+
work?: { item?: string; holder?: string };
|
|
991
|
+
/** Called when a work-lease renewal fails without losing the lease; the next beat retries it. */
|
|
992
|
+
onWorkLeaseWarning?: (message: string) => void;
|
|
792
993
|
}
|
|
793
994
|
|
|
794
995
|
/**
|
|
@@ -797,9 +998,10 @@ export interface RunOpOptions {
|
|
|
797
998
|
* Resolves with the run result when every phase succeeds (`status: "ok"`), and
|
|
798
999
|
* also when the run stopped at an unapproved gate (`status: "gated"`, with the
|
|
799
1000
|
* pending fact on `result.gate`) — a gate is a fact, not an error, so it is not
|
|
800
|
-
* thrown.
|
|
801
|
-
*
|
|
802
|
-
*
|
|
1001
|
+
* thrown. So does an activity's open decision point (`status: "waiting"`, with
|
|
1002
|
+
* the question on `result.point`, #2749). Rejects with `OpRunFailure` (carrying the partial result) on terminal
|
|
1003
|
+
* failure, after running any `onFailure` phases in reverse order; a gated or
|
|
1004
|
+
* waiting run runs no `onFailure` phase, because nothing failed and nothing was left
|
|
803
1005
|
* half-applied to compensate for.
|
|
804
1006
|
*
|
|
805
1007
|
* Whichever of the three ways it settles, the run carries an
|
|
@@ -818,6 +1020,27 @@ export async function runOpLocally(
|
|
|
818
1020
|
// The run id is the gate ledger's and the run ledger's alike: a pending gate
|
|
819
1021
|
// fact and the record naming that gate carry the same string.
|
|
820
1022
|
const runId = options.runId ?? randomUUID();
|
|
1023
|
+
// In a steward's turn (#2749: `chant operator --steward`, or `chant acp
|
|
1024
|
+
// --steward` on Fountain) the run is the steward's: its record names the
|
|
1025
|
+
// steward, and a decision point asked during it names this run.
|
|
1026
|
+
const turn = currentStewardTurn();
|
|
1027
|
+
const steward = options.steward ?? turn?.steward;
|
|
1028
|
+
const restore = turn && turn.run !== runId ? enterStewardTurn({ ...turn, run: runId }) : undefined;
|
|
1029
|
+
try {
|
|
1030
|
+
return await runOpInTurn(config, activities, profiles, signal, { ...options, runId, ...(steward ? { steward } : {}) });
|
|
1031
|
+
} finally {
|
|
1032
|
+
restore?.();
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
async function runOpInTurn(
|
|
1037
|
+
config: OpConfig,
|
|
1038
|
+
activities: Map<string, ActivityFn>,
|
|
1039
|
+
profiles: Record<string, ActivityProfile>,
|
|
1040
|
+
signal: AbortSignal | undefined,
|
|
1041
|
+
options: RunOpOptions & { runId: string },
|
|
1042
|
+
): Promise<OpRunResult> {
|
|
1043
|
+
const runId = options.runId;
|
|
821
1044
|
|
|
822
1045
|
const gates: GateContext = {
|
|
823
1046
|
op: config.name,
|
|
@@ -839,6 +1062,68 @@ export async function runOpLocally(
|
|
|
839
1062
|
}
|
|
840
1063
|
}
|
|
841
1064
|
|
|
1065
|
+
// The work lease (#2748). An Op that changes the checkout without one, or
|
|
1066
|
+
// whose lease names no item and whose run names none, is refused before
|
|
1067
|
+
// anything runs.
|
|
1068
|
+
const workProblems = workLeaseProblems(config);
|
|
1069
|
+
if (workProblems.length > 0) throw new Error(workProblems.join("\n"));
|
|
1070
|
+
if (workLeaseNeedsRunItem(config) && !options.work?.item) {
|
|
1071
|
+
throw new Error(
|
|
1072
|
+
`Op "${config.name}" runs under a work item's lease and names no item: pass one with \`chant run ${config.name} --work <id>\``,
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
const work = config.workLease
|
|
1076
|
+
? new RunWorkLease(config, {
|
|
1077
|
+
cwd: options.ledger?.cwd ?? options.cwd ?? process.cwd(),
|
|
1078
|
+
...(options.work?.holder ? { holder: options.work.holder } : {}),
|
|
1079
|
+
...(options.work?.item ? { item: options.work.item } : {}),
|
|
1080
|
+
...(options.onWorkLeaseWarning ? { onRenewError: options.onWorkLeaseWarning } : {}),
|
|
1081
|
+
})
|
|
1082
|
+
: undefined;
|
|
1083
|
+
if (work) gates.work = work;
|
|
1084
|
+
|
|
1085
|
+
// Steps run under a signal that also fires when the work lease is lost, so
|
|
1086
|
+
// the step in flight stops (chud's dispatcher stopped its agent the same way).
|
|
1087
|
+
let stepSignal = signal;
|
|
1088
|
+
if (work) {
|
|
1089
|
+
const both = new AbortController();
|
|
1090
|
+
const forward = () => both.abort();
|
|
1091
|
+
if (signal?.aborted) both.abort();
|
|
1092
|
+
else signal?.addEventListener("abort", forward, { once: true });
|
|
1093
|
+
work.lostSignal.addEventListener("abort", forward, { once: true });
|
|
1094
|
+
stepSignal = both.signal;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/** Release (or not) the work lease once the run's outcome is settled. */
|
|
1098
|
+
const finishWork = async (status: OpRunResult["status"]): Promise<{ workLease?: WorkLeaseRunResult }> =>
|
|
1099
|
+
work ? { workLease: await work.finish(status, resultsById) } : {};
|
|
1100
|
+
|
|
1101
|
+
/** The record a lost lease leaves, naming why. */
|
|
1102
|
+
const lostRecord = (): StepRecord => ({
|
|
1103
|
+
phase: UNATTRIBUTED_PHASE,
|
|
1104
|
+
fn: "workLease:lost",
|
|
1105
|
+
args: {},
|
|
1106
|
+
status: "fail",
|
|
1107
|
+
durationMs: 0,
|
|
1108
|
+
error: `${LEASE_LOST}: ${work?.lost ?? "the lease was lost"}. The run stopped and was not recorded as done.`,
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
const skipLaterPhases = (after: string) => {
|
|
1112
|
+
const stoppedAt = config.phases.findIndex((p) => p.name === after);
|
|
1113
|
+
if (stoppedAt < 0) return;
|
|
1114
|
+
for (const phase of config.phases.slice(stoppedAt + 1)) {
|
|
1115
|
+
for (const step of phase.steps) {
|
|
1116
|
+
records.push(
|
|
1117
|
+
isEffect(step)
|
|
1118
|
+
? skippedRecord(phase.name, `effect:${step.receipt.name}`)
|
|
1119
|
+
: isGate(step)
|
|
1120
|
+
? skippedRecord(phase.name, gateFn(step))
|
|
1121
|
+
: skippedRecord(phase.name, step.fn, step.args),
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
|
|
842
1127
|
const records: StepRecord[] = [];
|
|
843
1128
|
const start = Date.now();
|
|
844
1129
|
const startedAt = options.now ?? new Date(start).toISOString();
|
|
@@ -851,13 +1136,18 @@ export async function runOpLocally(
|
|
|
851
1136
|
settled: StepRecord[],
|
|
852
1137
|
status: OpRunResult["status"],
|
|
853
1138
|
gate?: PendingGateRecord,
|
|
1139
|
+
point?: WaitingPoint,
|
|
854
1140
|
): Promise<OpRunRecord> => {
|
|
1141
|
+
const ended = options.now ?? new Date().toISOString();
|
|
855
1142
|
const input = buildRunRecord(config, settled, {
|
|
856
1143
|
started: startedAt,
|
|
857
|
-
ended
|
|
1144
|
+
ended,
|
|
858
1145
|
status,
|
|
859
1146
|
id: runId,
|
|
860
|
-
|
|
1147
|
+
// A gate recorded under another op (#2779: a command's own gate) names it.
|
|
1148
|
+
...(gate ? { gate: { name: gate.gate, since: gate.timestamp, ...(gate.op !== config.name ? { op: gate.op } : {}) } } : {}),
|
|
1149
|
+
...(point ? { point: { ...point, since: ended } } : {}),
|
|
1150
|
+
...(options.steward ? { steward: options.steward } : {}),
|
|
861
1151
|
});
|
|
862
1152
|
const record: OpRunRecord = { version: 1, ...input, id: runId };
|
|
863
1153
|
if (!options.ledger) return record;
|
|
@@ -877,25 +1167,53 @@ export async function runOpLocally(
|
|
|
877
1167
|
try {
|
|
878
1168
|
for (const phase of config.phases) {
|
|
879
1169
|
if (signal?.aborted) throw new PhaseFailure([]);
|
|
880
|
-
records.push(...(await runPhase(phase, activities, profiles, resultsById, gates,
|
|
1170
|
+
records.push(...(await runPhase(phase, activities, profiles, resultsById, gates, stepSignal)));
|
|
881
1171
|
}
|
|
1172
|
+
// The last renewal before the run is recorded (#2748): a run whose lease
|
|
1173
|
+
// was lost is never recorded as done.
|
|
1174
|
+
if (work && !(await work.fence())) throw new WorkStop([], UNATTRIBUTED_PHASE, true);
|
|
882
1175
|
} catch (err) {
|
|
1176
|
+
// Nothing to claim, or every candidate held (#2748): not a failure. The
|
|
1177
|
+
// rest of the run is skipped and the claim's record says why.
|
|
1178
|
+
if (err instanceof WorkStop && !err.lost) {
|
|
1179
|
+
records.push(...err.records);
|
|
1180
|
+
skipLaterPhases(err.phase);
|
|
1181
|
+
const record = await settle(records, "ok");
|
|
1182
|
+
return {
|
|
1183
|
+
op: config.name,
|
|
1184
|
+
records,
|
|
1185
|
+
totalMs: Date.now() - start,
|
|
1186
|
+
status: "ok",
|
|
1187
|
+
startedAt,
|
|
1188
|
+
record,
|
|
1189
|
+
...(await finishWork("ok")),
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// An open decision point ends the run the way a pending gate does (#2749):
|
|
1194
|
+
// no later phase and no compensation, and the run is `waiting`.
|
|
1195
|
+
if (err instanceof PointStop) {
|
|
1196
|
+
records.push(...err.records);
|
|
1197
|
+
skipLaterPhases(err.phase);
|
|
1198
|
+
const record = await settle(records, "waiting", undefined, err.question);
|
|
1199
|
+
return {
|
|
1200
|
+
op: config.name,
|
|
1201
|
+
records,
|
|
1202
|
+
totalMs: Date.now() - start,
|
|
1203
|
+
status: "waiting",
|
|
1204
|
+
startedAt,
|
|
1205
|
+
point: err.question,
|
|
1206
|
+
record,
|
|
1207
|
+
...(await finishWork("waiting")),
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
|
|
883
1211
|
// A pending gate ends the run where it stands: no later phase, and no
|
|
884
1212
|
// `onFailure` compensation — nothing failed, so there is nothing to undo.
|
|
885
1213
|
if (err instanceof GateStop) {
|
|
886
1214
|
records.push(...err.records);
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
for (const step of phase.steps) {
|
|
890
|
-
records.push(
|
|
891
|
-
isEffect(step)
|
|
892
|
-
? skippedRecord(phase.name, `effect:${step.receipt.name}`)
|
|
893
|
-
: isGate(step)
|
|
894
|
-
? skippedRecord(phase.name, gateFn(step))
|
|
895
|
-
: skippedRecord(phase.name, step.fn, step.args),
|
|
896
|
-
);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
1215
|
+
skipLaterPhases(err.phase);
|
|
1216
|
+
const record = await settle(records, "gated", err.pending);
|
|
899
1217
|
return {
|
|
900
1218
|
op: config.name,
|
|
901
1219
|
records,
|
|
@@ -905,11 +1223,17 @@ export async function runOpLocally(
|
|
|
905
1223
|
gate: err.pending,
|
|
906
1224
|
...(err.pushed !== undefined ? { gatePushed: err.pushed } : {}),
|
|
907
1225
|
...(err.pushWarning ? { gatePushWarning: err.pushWarning } : {}),
|
|
908
|
-
record
|
|
1226
|
+
record,
|
|
1227
|
+
...(await finishWork("gated")),
|
|
909
1228
|
};
|
|
910
1229
|
}
|
|
911
1230
|
|
|
912
|
-
if (err instanceof
|
|
1231
|
+
if (err instanceof WorkStop) {
|
|
1232
|
+
// The lease was lost (#2748): the rest is skipped, and the lost record
|
|
1233
|
+
// below says why.
|
|
1234
|
+
records.push(...err.records);
|
|
1235
|
+
skipLaterPhases(err.phase);
|
|
1236
|
+
} else if (err instanceof PhaseFailure) {
|
|
913
1237
|
records.push(...err.records);
|
|
914
1238
|
} else {
|
|
915
1239
|
// Everything else the phase loop can throw (#2301). `PhaseFailure` and
|
|
@@ -934,14 +1258,20 @@ export async function runOpLocally(
|
|
|
934
1258
|
});
|
|
935
1259
|
}
|
|
936
1260
|
|
|
1261
|
+
// A step that failed because the lease was lost mid-step, or a lease lost
|
|
1262
|
+
// at any boundary: one record names it (#2748).
|
|
1263
|
+
if (work?.lost !== undefined) records.push(lostRecord());
|
|
1264
|
+
|
|
937
1265
|
// Compensation: run onFailure phases in reverse order (best-effort). Skipped
|
|
938
1266
|
// on abort (Ctrl-C) — the user asked to stop, so don't start new work.
|
|
1267
|
+
// It runs whatever the work lease did, so it takes no work boundary.
|
|
1268
|
+
const compensation: GateContext = { ...gates, work: undefined };
|
|
939
1269
|
if (!signal?.aborted) {
|
|
940
1270
|
for (const phase of [...(config.onFailure ?? [])].reverse()) {
|
|
941
1271
|
try {
|
|
942
|
-
records.push(...(await runPhase(phase, activities, profiles, resultsById,
|
|
1272
|
+
records.push(...(await runPhase(phase, activities, profiles, resultsById, compensation, signal)));
|
|
943
1273
|
} catch (compErr) {
|
|
944
|
-
if (compErr instanceof PhaseFailure || compErr instanceof GateStop) {
|
|
1274
|
+
if (compErr instanceof PhaseFailure || compErr instanceof GateStop || compErr instanceof PointStop) {
|
|
945
1275
|
records.push(...compErr.records);
|
|
946
1276
|
} else {
|
|
947
1277
|
// The same swallow, one level down (#2301): a compensation phase
|
|
@@ -959,6 +1289,7 @@ export async function runOpLocally(
|
|
|
959
1289
|
}
|
|
960
1290
|
}
|
|
961
1291
|
|
|
1292
|
+
const failed = await settle(records, "fail");
|
|
962
1293
|
throw new OpRunFailure(
|
|
963
1294
|
{
|
|
964
1295
|
op: config.name,
|
|
@@ -966,19 +1297,22 @@ export async function runOpLocally(
|
|
|
966
1297
|
totalMs: Date.now() - start,
|
|
967
1298
|
status: "fail",
|
|
968
1299
|
startedAt,
|
|
969
|
-
record:
|
|
1300
|
+
record: failed,
|
|
1301
|
+
...(await finishWork("fail")),
|
|
970
1302
|
},
|
|
971
1303
|
{ cause: err },
|
|
972
1304
|
);
|
|
973
1305
|
}
|
|
974
1306
|
|
|
1307
|
+
const record = await settle(records, "ok");
|
|
975
1308
|
return {
|
|
976
1309
|
op: config.name,
|
|
977
1310
|
records,
|
|
978
1311
|
totalMs: Date.now() - start,
|
|
979
1312
|
status: "ok",
|
|
980
1313
|
startedAt,
|
|
981
|
-
record
|
|
1314
|
+
record,
|
|
1315
|
+
...(await finishWork("ok")),
|
|
982
1316
|
};
|
|
983
1317
|
}
|
|
984
1318
|
|
|
@@ -75,6 +75,23 @@ const GATED: OpRunResult = withRecord({
|
|
|
75
75
|
],
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
/** A gate bound to a plan (#2300), for the --plan tests (#2832). */
|
|
79
|
+
const GATED_WITH_PLAN: OpRunResult = withRecord({
|
|
80
|
+
op: "prod-apply",
|
|
81
|
+
totalMs: 300,
|
|
82
|
+
status: "gated",
|
|
83
|
+
startedAt: "2026-09-05T12:00:00.000Z",
|
|
84
|
+
gate: {
|
|
85
|
+
version: 1, kind: "pending", op: "prod-apply", gate: "rollout-gate",
|
|
86
|
+
timestamp: "2026-09-05T12:00:00.000Z",
|
|
87
|
+
expiresAt: "2026-09-07T12:00:00.000Z",
|
|
88
|
+
planDigest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
89
|
+
},
|
|
90
|
+
records: [
|
|
91
|
+
{ phase: "Apply", fn: "gate:rollout-gate", args: {}, status: "skipped", durationMs: 0 },
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
|
|
78
95
|
describe("renderHuman — gated (#2119)", () => {
|
|
79
96
|
test("names the gate, the approve line and the expiry, and points at no other runtime", () => {
|
|
80
97
|
const lines: string[] = [];
|
|
@@ -88,6 +105,14 @@ describe("renderHuman — gated (#2119)", () => {
|
|
|
88
105
|
expect(out).not.toContain("--on");
|
|
89
106
|
});
|
|
90
107
|
|
|
108
|
+
test("a gate bound to a plan (#2300) carries --plan on the approve line (#2832)", () => {
|
|
109
|
+
const lines: string[] = [];
|
|
110
|
+
renderHuman(GATED_WITH_PLAN, (l) => lines.push(l));
|
|
111
|
+
const out = lines.join("\n");
|
|
112
|
+
expect(out).toContain(`plan : ${GATED_WITH_PLAN.gate!.planDigest}`);
|
|
113
|
+
expect(out).toContain(`approve : chant approve prod-apply rollout-gate --plan ${GATED_WITH_PLAN.gate!.planDigest}`);
|
|
114
|
+
});
|
|
115
|
+
|
|
91
116
|
test("shows the approver on a gate that passed", () => {
|
|
92
117
|
const lines: string[] = [];
|
|
93
118
|
renderHuman(withRecord({
|
|
@@ -157,6 +182,14 @@ describe("renderJson", () => {
|
|
|
157
182
|
expect(parsed.approve).toBe("chant approve prod-apply rollout-gate");
|
|
158
183
|
});
|
|
159
184
|
|
|
185
|
+
test("a gate bound to a plan (#2300) carries --plan on the approve line (#2832)", () => {
|
|
186
|
+
const lines: string[] = [];
|
|
187
|
+
renderJson(GATED_WITH_PLAN, (l) => lines.push(l));
|
|
188
|
+
const parsed = JSON.parse(lines[0]) as OpRunRecord & { approve: string };
|
|
189
|
+
expect(parsed.status).toBe("gated");
|
|
190
|
+
expect(parsed.approve).toBe(`chant approve prod-apply rollout-gate --plan ${GATED_WITH_PLAN.gate!.planDigest}`);
|
|
191
|
+
});
|
|
192
|
+
|
|
160
193
|
// #2310: a JSON consumer (CI tooling reading `chant run --json`) needs the
|
|
161
194
|
// same fact the human render shows — not just a silent success.
|
|
162
195
|
test("a gated record whose push failed carries pushed:false and why", () => {
|