@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
|
@@ -22,10 +22,15 @@ import {
|
|
|
22
22
|
STATUS_GATE_REASON_CODES,
|
|
23
23
|
STATUS_OUTPUT_SCHEMA_ID,
|
|
24
24
|
STATUS_REASON_CODES,
|
|
25
|
+
STATUS_STEWARD_REASON_CODES,
|
|
25
26
|
workspaceStatus,
|
|
26
27
|
type StatusDocument,
|
|
27
28
|
} from "./status";
|
|
28
29
|
import schema from "./status.schema.json";
|
|
30
|
+
import { appendRunRecord } from "../lifecycle/run-ledger";
|
|
31
|
+
import { acquireStewardLease } from "../op/operator";
|
|
32
|
+
import { stewardWorkHolder } from "../op/work-lease-run";
|
|
33
|
+
import { claimWorkLease } from "../lifecycle/work-lease";
|
|
29
34
|
|
|
30
35
|
const REPO = join(import.meta.dirname, "..", "..", "..", "..");
|
|
31
36
|
|
|
@@ -148,6 +153,7 @@ describe("status output schema", () => {
|
|
|
148
153
|
expect(schema.$defs.environment.properties.reason.oneOf[1].properties!.code.enum).toEqual([...STATUS_REASON_CODES]);
|
|
149
154
|
expect(schema.$defs.failure.properties.error.properties.code.enum).toEqual([...STATUS_ERROR_CODES]);
|
|
150
155
|
expect(schema.$defs.gateLedger.properties.reason.oneOf[1].properties!.code.enum).toEqual([...STATUS_GATE_REASON_CODES]);
|
|
156
|
+
expect(schema.$defs.member.properties.stewardReasons.items.properties.code.enum).toEqual([...STATUS_STEWARD_REASON_CODES]);
|
|
151
157
|
});
|
|
152
158
|
});
|
|
153
159
|
|
|
@@ -163,6 +169,8 @@ describe("chant workspace status on built workspaces", () => {
|
|
|
163
169
|
expect(web.environments[0].ledger).toEqual({ layout: "members", path: "_members/web/staging/releases.jsonl", shared: false });
|
|
164
170
|
// The later of the two records wins.
|
|
165
171
|
expect(web.environments[0].releases.map((r) => [r.component, r.digest, r.gitSha])).toEqual([["web", D("a"), "a".repeat(40)]]);
|
|
172
|
+
// No plan was ever written for these digests (ws-055, #2733): the field is present, and null.
|
|
173
|
+
expect(web.environments[0].releases.map((r) => r.plan)).toEqual([null]);
|
|
166
174
|
// api has no _members/api/ yet, so it falls back to the flat ledger, which the root member reads too.
|
|
167
175
|
expect(api.environments[0].ledger).toEqual({ layout: "flat", path: "staging/releases.jsonl", shared: true });
|
|
168
176
|
expect(site.environments[0].ledger).toEqual({ layout: "flat", path: "staging/releases.jsonl", shared: true });
|
|
@@ -199,6 +207,33 @@ describe("chant workspace status on built workspaces", () => {
|
|
|
199
207
|
expect(text).toContain("3 members, 3 with a release in staging, 2 differ from prod, 0 unreadable");
|
|
200
208
|
});
|
|
201
209
|
|
|
210
|
+
test("resolves a release's plan from _plans/<digest>.json, member-scoped per #2524 D7 (ws-055, #2733)", async () => {
|
|
211
|
+
const root = repo({
|
|
212
|
+
"chant.workspace.json": declaration([
|
|
213
|
+
{ name: "site", dir: ".", kind: "other", because: "the root project" },
|
|
214
|
+
{ name: "web", dir: "apps/web", kind: "chant" },
|
|
215
|
+
]),
|
|
216
|
+
"apps/web/chant.config.ts": "",
|
|
217
|
+
});
|
|
218
|
+
const webPlan = { digest: D("a"), release: "r-web", units: [{ id: "w-1" }] };
|
|
219
|
+
const apiPlan = { digest: D("b"), release: "r-api", units: [] };
|
|
220
|
+
lifecycle(root, {
|
|
221
|
+
"_members/web/staging/releases.jsonl": jsonl(release("web", "staging", D("a"), "a".repeat(40))),
|
|
222
|
+
[`_members/web/_plans/sha256_${"a".repeat(64)}.json`]: JSON.stringify(webPlan),
|
|
223
|
+
"staging/releases.jsonl": jsonl(release("api", "staging", D("b"), "b".repeat(40)), release("worker", "staging", D("c"), "c".repeat(40))),
|
|
224
|
+
[`_plans/sha256_${"b".repeat(64)}.json`]: JSON.stringify(apiPlan),
|
|
225
|
+
});
|
|
226
|
+
const doc = result(await workspaceStatus({ cwd: root, env: "staging" }));
|
|
227
|
+
expectValid(doc);
|
|
228
|
+
const web = doc.members.find((m) => m.name === "web")!;
|
|
229
|
+
const site = doc.members.find((m) => m.name === "site")!;
|
|
230
|
+
expect(web.environments[0].releases[0].plan).toEqual(webPlan);
|
|
231
|
+
const byComponent = new Map(site.environments[0].releases.map((r) => [r.component, r]));
|
|
232
|
+
expect(byComponent.get("api")?.plan).toEqual(apiPlan);
|
|
233
|
+
// worker has no plan stored under its digest: reads null, not an error.
|
|
234
|
+
expect(byComponent.get("worker")?.plan).toBeNull();
|
|
235
|
+
});
|
|
236
|
+
|
|
202
237
|
test("compares on the input digest when a release has one", async () => {
|
|
203
238
|
const root = repo({ "chant.workspace.json": declaration([{ name: "chart", dir: "chart", kind: "other", because: "helm" }]) });
|
|
204
239
|
lifecycle(root, {
|
|
@@ -253,6 +288,33 @@ describe("chant workspace status on built workspaces", () => {
|
|
|
253
288
|
expect(formatStatus(doc)).toContain("web - no release");
|
|
254
289
|
});
|
|
255
290
|
|
|
291
|
+
test("each member lists its box block: the capability, its broker and its scope (#2726)", async () => {
|
|
292
|
+
const root = repo({
|
|
293
|
+
"chant.workspace.json": declaration([
|
|
294
|
+
{ name: "web", dir: "web", kind: "chant" },
|
|
295
|
+
{
|
|
296
|
+
name: "box",
|
|
297
|
+
dir: "box",
|
|
298
|
+
kind: "other",
|
|
299
|
+
because: "the box's declarations",
|
|
300
|
+
box: { capabilities: [{ name: "fountain", broker: "lobby", scope: ["agent", "vault", "conversations", "sandboxes"] }, { name: "inference" }] },
|
|
301
|
+
},
|
|
302
|
+
]),
|
|
303
|
+
});
|
|
304
|
+
const doc = result(await workspaceStatus({ cwd: root, env: "prod" }));
|
|
305
|
+
expectValid(doc);
|
|
306
|
+
expect(doc.members.map((m) => m.box)).toEqual([
|
|
307
|
+
null,
|
|
308
|
+
{
|
|
309
|
+
capabilities: [
|
|
310
|
+
{ name: "fountain", broker: "lobby", scope: ["agent", "vault", "conversations", "sandboxes"] },
|
|
311
|
+
{ name: "inference", broker: null, scope: [] },
|
|
312
|
+
],
|
|
313
|
+
isolation: null,
|
|
314
|
+
},
|
|
315
|
+
]);
|
|
316
|
+
});
|
|
317
|
+
|
|
256
318
|
test("every failure validates with its code", async () => {
|
|
257
319
|
const empty = repo({});
|
|
258
320
|
const outside = realpathSync(mkdtempSync(join(tmpdir(), "chant-status-nogit-")));
|
|
@@ -357,7 +419,7 @@ describe("gate state in chant workspace status --json (#2674)", () => {
|
|
|
357
419
|
expiresAt: "2026-12-31T00:00:00.000Z",
|
|
358
420
|
approvals: [{ principal: "alice", channel: "cli", at: "2026-09-20T01:00:00.000Z" }],
|
|
359
421
|
needed: 1,
|
|
360
|
-
approve:
|
|
422
|
+
approve: `chant approve web deploy --env staging --plan ${D("1")}`,
|
|
361
423
|
},
|
|
362
424
|
{
|
|
363
425
|
component: "web",
|
|
@@ -369,7 +431,7 @@ describe("gate state in chant workspace status --json (#2674)", () => {
|
|
|
369
431
|
expiresAt: "2026-12-31T00:00:00.000Z",
|
|
370
432
|
approvals: [],
|
|
371
433
|
needed: 1,
|
|
372
|
-
approve:
|
|
434
|
+
approve: `chant approve web deploy --env prod --plan ${D("2")}`,
|
|
373
435
|
},
|
|
374
436
|
{
|
|
375
437
|
component: "web",
|
|
@@ -382,15 +444,15 @@ describe("gate state in chant workspace status --json (#2674)", () => {
|
|
|
382
444
|
// The agent's approval doesn't count toward the quorum.
|
|
383
445
|
approvals: [{ principal: "bob", channel: "cli", at: "2026-09-20T02:00:00.000Z" }],
|
|
384
446
|
needed: 2,
|
|
385
|
-
approve:
|
|
447
|
+
approve: `chant approve web review --env staging --plan ${D("4")}`,
|
|
386
448
|
},
|
|
387
449
|
]);
|
|
388
450
|
// api and the root member both read the flat _gates/.
|
|
389
451
|
expect(api.gateLedger).toEqual({ layout: "flat", path: "_gates", shared: true, malformed: 1, reason: null });
|
|
390
452
|
expect(site.gates).toEqual(api.gates);
|
|
391
453
|
expect(api.gates.map((g) => [g.component, g.name, g.env, g.state, g.approve])).toEqual([
|
|
392
|
-
["api", "deploy", "staging", "expired",
|
|
393
|
-
["api", "smoke", "staging", "superseded",
|
|
454
|
+
["api", "deploy", "staging", "expired", `chant approve api deploy --env staging --plan ${D("5")}`],
|
|
455
|
+
["api", "smoke", "staging", "superseded", `chant approve api smoke --env staging --plan ${D("6")}`],
|
|
394
456
|
["nightly", "confirm", null, "approved", "chant approve nightly confirm"],
|
|
395
457
|
]);
|
|
396
458
|
// The superseding approval named another plan, so it isn't listed as one that counts.
|
|
@@ -452,13 +514,134 @@ describe("gate state in chant workspace status --json (#2674)", () => {
|
|
|
452
514
|
expect(await runWorkspaceStatus({ args: { extraPositional: "staging", extraPositional2: root, json: true } } as unknown as CommandContext)).toBe(0);
|
|
453
515
|
const printed = JSON.parse(String(log.mock.calls.at(-1)?.[0])) as StatusDocument;
|
|
454
516
|
expectValid(printed);
|
|
455
|
-
expect(result(printed).members.find((m) => m.name === "web")!.gates.map((g) => g.approve)).toContain(
|
|
517
|
+
expect(result(printed).members.find((m) => m.name === "web")!.gates.map((g) => g.approve)).toContain(`chant approve web deploy --env staging --plan ${D("1")}`);
|
|
456
518
|
} finally {
|
|
457
519
|
log.mockRestore();
|
|
458
520
|
}
|
|
459
521
|
});
|
|
460
522
|
});
|
|
461
523
|
|
|
524
|
+
describe("stewards in chant workspace status --json (#2731)", () => {
|
|
525
|
+
const op = (name: string, cron?: string) => ({
|
|
526
|
+
name,
|
|
527
|
+
overview: name,
|
|
528
|
+
phases: [{ name: "Run", steps: [{ kind: "activity", fn: "noop", args: {} }] }],
|
|
529
|
+
...(cron ? { schedule: { cron, overlap: "skip" } } : {}),
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
/** A box member declaring one steward, local by default and fountain on fountain-k3d. */
|
|
533
|
+
function stewarded(): string {
|
|
534
|
+
const steward = {
|
|
535
|
+
kind: "Chant::Steward",
|
|
536
|
+
name: "box-steward",
|
|
537
|
+
ops: [
|
|
538
|
+
op("box-converge", "* * * * *"),
|
|
539
|
+
op("box-release"),
|
|
540
|
+
{ ...op("box-dispatch", "*/5 * * * *"), workLease: { item: ["W-1", "W-2"] }, changesCheckout: true },
|
|
541
|
+
],
|
|
542
|
+
form: { default: "local", environments: { "fountain-k3d": "fountain" } },
|
|
543
|
+
capabilities: ["fountain", "inference"],
|
|
544
|
+
vault: null,
|
|
545
|
+
};
|
|
546
|
+
const root = repo({
|
|
547
|
+
"chant.workspace.json": declaration([
|
|
548
|
+
{ name: "box", dir: "box", kind: "chant", box: { capabilities: [{ name: "fountain", broker: "lobby", scope: ["agent", "vault"] }] } },
|
|
549
|
+
{ name: "notes", dir: "notes", kind: "other", because: "prose" },
|
|
550
|
+
]),
|
|
551
|
+
"box/chant.config.ts": "export default {};\n",
|
|
552
|
+
"box/ops/steward.op.ts": `export const steward = ${JSON.stringify(steward)};\n`,
|
|
553
|
+
"notes/README.md": "notes\n",
|
|
554
|
+
});
|
|
555
|
+
lifecycle(root, { "README": "ledger\n" });
|
|
556
|
+
return root;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
test("names the steward, its form in the environment asked for, its Ops, schedules, last runs and lease", async () => {
|
|
560
|
+
const root = stewarded();
|
|
561
|
+
const box = join(root, "box");
|
|
562
|
+
await appendRunRecord(
|
|
563
|
+
{ op: "box-converge", env: "local", started: "2026-09-25T10:00:00.000Z", ended: "2026-09-25T10:00:05.000Z", status: "ok", labels: {}, outcomes: {}, phases: [] },
|
|
564
|
+
{ cwd: box },
|
|
565
|
+
);
|
|
566
|
+
expect((await acquireStewardLease("box-steward", "box-host:1:abc", { cwd: box, now: () => new Date("2026-09-25T10:00:00.000Z") })).acquired).toBe(true);
|
|
567
|
+
// #2748: the dispatch turn holds W-1; someone else holds W-2.
|
|
568
|
+
const turn = await claimWorkLease("W-1", stewardWorkHolder("box-steward", "box-dispatch", "box-host:1:abc"), { cwd: box, ttlMs: 600_000, now: () => new Date("2026-09-25T10:00:30.000Z") });
|
|
569
|
+
expect(turn.ok).toBe(true);
|
|
570
|
+
expect((await claimWorkLease("W-2", "someone-else", { cwd: box, ttlMs: 600_000, now: () => new Date("2026-09-25T10:00:30.000Z") })).ok).toBe(true);
|
|
571
|
+
|
|
572
|
+
const doc = result(await workspaceStatus({ cwd: root, env: "minimal", now: "2026-09-25T10:01:00.000Z" }));
|
|
573
|
+
expectValid(doc);
|
|
574
|
+
const [boxMember, notes] = doc.members;
|
|
575
|
+
expect(notes.stewards).toEqual([]);
|
|
576
|
+
expect(boxMember.stewardReasons).toEqual([]);
|
|
577
|
+
expect(boxMember.stewards).toHaveLength(1);
|
|
578
|
+
const s = boxMember.stewards[0];
|
|
579
|
+
expect(s).toMatchObject({
|
|
580
|
+
name: "box-steward",
|
|
581
|
+
file: "ops/steward.op.ts",
|
|
582
|
+
form: "local",
|
|
583
|
+
forms: { default: "local", environments: { "fountain-k3d": "fountain" } },
|
|
584
|
+
// #2726: the steward holds no credential; its capabilities join the box block.
|
|
585
|
+
vault: null,
|
|
586
|
+
capabilities: [
|
|
587
|
+
{ name: "fountain", broker: "lobby", declared: true },
|
|
588
|
+
{ name: "inference", broker: null, declared: false },
|
|
589
|
+
],
|
|
590
|
+
lease: { holder: "box-host:1:abc", acquiredAt: "2026-09-25T10:00:00.000Z", live: true },
|
|
591
|
+
});
|
|
592
|
+
expect(s.ops).toEqual([
|
|
593
|
+
{
|
|
594
|
+
name: "box-converge",
|
|
595
|
+
schedule: { cron: "* * * * *", overlap: "skip" },
|
|
596
|
+
env: "local",
|
|
597
|
+
lastRun: { id: expect.any(String), status: "ok", started: "2026-09-25T10:00:00.000Z", ended: "2026-09-25T10:00:05.000Z", gate: null, point: null },
|
|
598
|
+
// #2778: not a ConvergeOp (no Converge label), so no tick.
|
|
599
|
+
lastTick: null,
|
|
600
|
+
changesCheckout: false,
|
|
601
|
+
workLease: null,
|
|
602
|
+
},
|
|
603
|
+
{ name: "box-release", schedule: null, env: "local", lastRun: null, lastTick: null, changesCheckout: false, workLease: null },
|
|
604
|
+
{
|
|
605
|
+
name: "box-dispatch",
|
|
606
|
+
schedule: { cron: "*/5 * * * *", overlap: "skip" },
|
|
607
|
+
env: "local",
|
|
608
|
+
lastRun: null,
|
|
609
|
+
lastTick: null,
|
|
610
|
+
changesCheckout: true,
|
|
611
|
+
workLease: {
|
|
612
|
+
kind: null,
|
|
613
|
+
held: [
|
|
614
|
+
{
|
|
615
|
+
item: "W-1",
|
|
616
|
+
holder: "box-steward/box-dispatch@box-host:1:abc",
|
|
617
|
+
token: turn.ok ? turn.lease.token : "",
|
|
618
|
+
acquiredAt: "2026-09-25T10:00:30.000Z",
|
|
619
|
+
expiresAt: "2026-09-25T10:10:30.000Z",
|
|
620
|
+
state: "active",
|
|
621
|
+
},
|
|
622
|
+
],
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
]);
|
|
626
|
+
// #2749: nothing the steward runs waits on a decision point.
|
|
627
|
+
expect(s.waiting).toEqual([]);
|
|
628
|
+
|
|
629
|
+
const k3d = result(await workspaceStatus({ cwd: root, env: "fountain-k3d", now: "2026-09-25T11:00:00.000Z" }));
|
|
630
|
+
expectValid(k3d);
|
|
631
|
+
expect(k3d.members[0].stewards[0].form).toBe("fountain");
|
|
632
|
+
expect(k3d.members[0].stewards[0].lease!.live).toBe(false);
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test("an op file that can't be imported is a reason, and the read still succeeds", async () => {
|
|
636
|
+
const root = stewarded();
|
|
637
|
+
writeFileSync(join(root, "box", "ops", "broken.op.ts"), "export const x = ;\n");
|
|
638
|
+
const doc = result(await workspaceStatus({ cwd: root, env: "minimal" }));
|
|
639
|
+
expectValid(doc);
|
|
640
|
+
expect(doc.members[0].stewards.map((s) => s.name)).toEqual(["box-steward"]);
|
|
641
|
+
expect(doc.members[0].stewardReasons.map((r) => r.code)).toEqual(["stewards-unreadable"]);
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
|
|
462
645
|
describe("chant workspace status on the chant repo (#2557)", () => {
|
|
463
646
|
test("validates and lists every declared member", async () => {
|
|
464
647
|
const doc = result(await workspaceStatus({ cwd: join(REPO, "packages", "core"), env: "prod", compareTo: "staging" }));
|
|
@@ -103,9 +103,14 @@ export const defaultGateLedgerReader: GateLedgerReader = async (dir, commit, cwd
|
|
|
103
103
|
return files;
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The exact `chant approve` line for a gate. A gate bound to a plan (#2300)
|
|
108
|
+
* gets `--plan <digest>` too (#2832), so running the line back approves the
|
|
109
|
+
* plan this read showed and not whatever happens to be pending by the time
|
|
110
|
+
* someone runs it.
|
|
111
|
+
*/
|
|
112
|
+
export function approveCommand(component: string, gate: string, env: string | null, planDigest?: string | null): string {
|
|
113
|
+
return `chant approve ${component} ${gate}${env !== null ? ` --env ${env}` : ""}${planDigest ? ` --plan ${planDigest}` : ""}`;
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
const approvalOf = (r: GateResolutionRecord): StatusGateApproval => ({ principal: r.resolvedBy, channel: r.origin ?? null, at: r.timestamp });
|
|
@@ -150,7 +155,7 @@ function decide(component: string, standing: PendingGateRecord, resolutions: Gat
|
|
|
150
155
|
expiresAt: standing.expiresAt,
|
|
151
156
|
approvals: approvals.map(approvalOf),
|
|
152
157
|
needed,
|
|
153
|
-
approve: approveCommand(component, gate, env),
|
|
158
|
+
approve: approveCommand(component, gate, env, planDigest),
|
|
154
159
|
};
|
|
155
160
|
}
|
|
156
161
|
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stewards in `chant workspace status --json` (#2731): which steward runs a
|
|
3
|
+
* member's operational work, the form it takes in the environment asked for,
|
|
4
|
+
* its Ops with their schedules, and each Op's last run.
|
|
5
|
+
*
|
|
6
|
+
* The steward itself is read from its declaration, the same `*.op.ts` files
|
|
7
|
+
* `chant operator --steward` reads (`../op/discover.ts`), and only for a member
|
|
8
|
+
* of kind `chant` that is a chant project of its own (a `chant.config.ts` or
|
|
9
|
+
* `.json` in its directory), so a member never lists a parent project's
|
|
10
|
+
* steward. Everything
|
|
11
|
+
* else is read from the local `chant/lifecycle` branch and refs, never
|
|
12
|
+
* fetched, as the rest of status is:
|
|
13
|
+
*
|
|
14
|
+
* - each Op's last run is the newest record in its run ledger,
|
|
15
|
+
* `<env>/runs__<op>.jsonl` under the member's ledger prefix, where `<env>`
|
|
16
|
+
* is the Op's own `labels.Env` or `local`;
|
|
17
|
+
* - `waiting` lists the open decision points the steward waits on (#2749):
|
|
18
|
+
* each Op whose newest run is the steward's own and stopped on a question,
|
|
19
|
+
* as the run ledger records it. The question's state now is `points`'s;
|
|
20
|
+
* - `lease` is the local steward's own lease ref
|
|
21
|
+
* (`refs/chant/lease/[<prefix>]_stewards/<name>`), present while a
|
|
22
|
+
* `chant operator --steward` holds it or until it expires;
|
|
23
|
+
* - an Op's `workLease.held` is the work leases its turns hold (#2748): those
|
|
24
|
+
* whose holder is `<steward>/<op>@...` (`stewardWorkHolder`), read from the
|
|
25
|
+
* ledger of the Op's work kind, or the member's own.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
29
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
30
|
+
import { discoverStewards } from "../op/discover";
|
|
31
|
+
import { stewardFormFor, stewardLeaseName, type StewardForm } from "../op/steward";
|
|
32
|
+
import { readRunLedger, runEnvOf } from "../lifecycle/run-ledger";
|
|
33
|
+
import { readConvergeLedger, type ConvergeTickRecord } from "../lifecycle/converge-ledger";
|
|
34
|
+
import { leaseRef } from "../lifecycle/lease";
|
|
35
|
+
import { readBlobBySha, readRefSha } from "../lifecycle/git";
|
|
36
|
+
import { resolveMemberLedger } from "../lifecycle/member-ledger";
|
|
37
|
+
import { listWorkLeases } from "../lifecycle/work-lease";
|
|
38
|
+
import { stewardWorkHolder } from "../op/work-lease-run";
|
|
39
|
+
import type { OpConfig } from "../op/types";
|
|
40
|
+
import type { OpRunRecord } from "../op/runtime";
|
|
41
|
+
import { approveCommand } from "./status-gates";
|
|
42
|
+
import type { ReasonCode } from "./reason-codes";
|
|
43
|
+
|
|
44
|
+
/** Why a member's stewards can't be fully listed. Closed: a new code is a contract change. */
|
|
45
|
+
export const STEWARD_REASON_CODES = [
|
|
46
|
+
/** An `*.op.ts` file could not be imported, so a steward it declares may be missing. */
|
|
47
|
+
"stewards-unreadable",
|
|
48
|
+
/** A steward was dropped: its name, or an Op it lists, belongs to another steward. */
|
|
49
|
+
"stewards-conflict",
|
|
50
|
+
/** Reading an Op's run ledger, or a ConvergeOp's converge ledger, failed, so its last run or last tick is null. */
|
|
51
|
+
"steward-runs-unreadable",
|
|
52
|
+
] as const satisfies readonly ReasonCode[];
|
|
53
|
+
export type StewardReasonCode = (typeof STEWARD_REASON_CODES)[number];
|
|
54
|
+
|
|
55
|
+
export interface StatusStewardRun {
|
|
56
|
+
id: string;
|
|
57
|
+
status: OpRunRecord["status"];
|
|
58
|
+
started: string;
|
|
59
|
+
ended: string;
|
|
60
|
+
/**
|
|
61
|
+
* The gate the run stopped at, for a `gated` run: its name, when the run
|
|
62
|
+
* stopped there, the op it is recorded under (the Op's own, or the
|
|
63
|
+
* command's for a step whose command stopped at its own gate, #2779, such
|
|
64
|
+
* as `workspace-upgrade`), and the command that approves it.
|
|
65
|
+
*/
|
|
66
|
+
gate: { name: string; since: string; op: string; approve: string } | null;
|
|
67
|
+
/** The open decision point the run stopped on, for a `waiting` run (#2749). */
|
|
68
|
+
point: StatusStewardWait | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** An open decision point a steward's run stopped on (#2749), as the run ledger records it. */
|
|
72
|
+
export interface StatusStewardWait {
|
|
73
|
+
/** The answer record's id: what `points --open` lists and `points answer` names. */
|
|
74
|
+
id: string;
|
|
75
|
+
point: string;
|
|
76
|
+
/** The question's state when the run stopped. */
|
|
77
|
+
state: "escalated" | "proposed";
|
|
78
|
+
path: string;
|
|
79
|
+
subject: string | null;
|
|
80
|
+
since: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A ConvergeOp's newest tick on the converge ledger (#2778). */
|
|
84
|
+
export interface StatusStewardTick {
|
|
85
|
+
/** The tick's id, or null for a tick recorded before ticks had ids. */
|
|
86
|
+
id: string | null;
|
|
87
|
+
timestamp: string;
|
|
88
|
+
/** The tick's one log line. */
|
|
89
|
+
log: string;
|
|
90
|
+
/** Every rule that fired. */
|
|
91
|
+
firedRuleIds: string[];
|
|
92
|
+
/** What each fired rule did, and for which resource on a ConvergeOp with an observer step. */
|
|
93
|
+
outcomes: {
|
|
94
|
+
ruleId: string;
|
|
95
|
+
action: ConvergeTickRecord["outcomes"][number]["action"];
|
|
96
|
+
op: string | null;
|
|
97
|
+
resource: string | null;
|
|
98
|
+
reason: string | null;
|
|
99
|
+
}[];
|
|
100
|
+
/** What the observer step reported, or null for a ConvergeOp that observes a lexicon environment. */
|
|
101
|
+
resources: { name: string; status: "in-sync" | "drifted" | "unknown"; detail: string | null }[] | null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface StatusStewardOp {
|
|
105
|
+
name: string;
|
|
106
|
+
/** The Op's cadence, or null for an Op the steward runs only when asked. */
|
|
107
|
+
schedule: { cron: string; overlap: "skip" } | null;
|
|
108
|
+
/** The environment its runs are recorded under: its `labels.Env`, or `local`. */
|
|
109
|
+
env: string;
|
|
110
|
+
/** The newest run in its run ledger, or null when it has none. */
|
|
111
|
+
lastRun: StatusStewardRun | null;
|
|
112
|
+
/** For a ConvergeOp, its newest tick on the converge ledger (#2778); null for any other Op, or before its first tick. */
|
|
113
|
+
lastTick: StatusStewardTick | null;
|
|
114
|
+
/** Whether the Op changes the checkout, and so runs under a work lease on a branch of its own (#2748). */
|
|
115
|
+
changesCheckout: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* The Op's work lease (#2748), or null when it declares none: the work kind
|
|
118
|
+
* file whose ledger holds it (null for the member's own), and the leases
|
|
119
|
+
* the steward's turns of this Op hold, a live one while a turn runs.
|
|
120
|
+
*/
|
|
121
|
+
workLease: { kind: string | null; held: StatusStewardWorkLease[] } | null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A work lease a steward's turn holds (#2748). */
|
|
125
|
+
export interface StatusStewardWorkLease {
|
|
126
|
+
item: string;
|
|
127
|
+
holder: string;
|
|
128
|
+
token: string;
|
|
129
|
+
acquiredAt: string;
|
|
130
|
+
expiresAt: string;
|
|
131
|
+
state: "active" | "expired";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface StatusSteward {
|
|
135
|
+
name: string;
|
|
136
|
+
/** The `*.op.ts` file declaring it, relative to the member's directory. */
|
|
137
|
+
file: string;
|
|
138
|
+
/** Its form in the environment status was asked for. */
|
|
139
|
+
form: StewardForm;
|
|
140
|
+
/** The declared form: a default and the environments that differ from it. */
|
|
141
|
+
forms: { default: StewardForm; environments: Record<string, StewardForm> };
|
|
142
|
+
/** The vault the steward holds on Fountain, by name, or null. */
|
|
143
|
+
vault: string | null;
|
|
144
|
+
/**
|
|
145
|
+
* The box capabilities the steward reaches through a broker (#2726), joined
|
|
146
|
+
* with the member's box block: `broker` is the block's, and `declared` is
|
|
147
|
+
* false when the block doesn't list the capability (or there is no block).
|
|
148
|
+
*/
|
|
149
|
+
capabilities: { name: string; broker: string | null; declared: boolean }[];
|
|
150
|
+
/** The local steward's own lease, or null when no local operator has held it. */
|
|
151
|
+
lease: { holder: string; acquiredAt: string; expiresAt: string; live: boolean } | null;
|
|
152
|
+
ops: StatusStewardOp[];
|
|
153
|
+
/**
|
|
154
|
+
* The open decision points the steward waits on (#2749): each Op whose
|
|
155
|
+
* newest run is the steward's own and stopped on a question.
|
|
156
|
+
*/
|
|
157
|
+
waiting: (StatusStewardWait & { op: string; run: string })[];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface MemberStewards {
|
|
161
|
+
stewards: StatusSteward[];
|
|
162
|
+
reasons: { code: StewardReasonCode; message: string }[];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function waitOf(p: NonNullable<OpRunRecord["point"]>): StatusStewardWait {
|
|
166
|
+
return {
|
|
167
|
+
id: p.id,
|
|
168
|
+
point: p.point,
|
|
169
|
+
state: p.state === "proposed" ? "proposed" : "escalated",
|
|
170
|
+
path: p.path,
|
|
171
|
+
subject: p.subject ?? null,
|
|
172
|
+
since: p.since,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function gateOf(g: NonNullable<OpRunRecord["gate"]>, opName: string): NonNullable<StatusStewardRun["gate"]> {
|
|
177
|
+
const op = g.op ?? opName;
|
|
178
|
+
return { name: g.name, since: g.since, op, approve: approveCommand(op, g.name, null) };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function tickOf(t: ConvergeTickRecord): StatusStewardTick {
|
|
182
|
+
return {
|
|
183
|
+
id: t.id ?? null,
|
|
184
|
+
timestamp: t.timestamp,
|
|
185
|
+
log: t.log,
|
|
186
|
+
firedRuleIds: [...t.firedRuleIds],
|
|
187
|
+
outcomes: t.outcomes.map((o) => ({
|
|
188
|
+
ruleId: o.ruleId,
|
|
189
|
+
action: o.action,
|
|
190
|
+
op: o.op ?? null,
|
|
191
|
+
resource: o.resource ?? null,
|
|
192
|
+
reason: o.reason ?? null,
|
|
193
|
+
})),
|
|
194
|
+
resources: t.resources ? t.resources.map((r) => ({ name: r.name, status: r.status, detail: r.detail ?? null })) : null,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Whether a directory is a chant project of its own. */
|
|
199
|
+
function isChantProject(dir: string): boolean {
|
|
200
|
+
return existsSync(join(dir, "chant.config.ts")) || existsSync(join(dir, "chant.config.json"));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function readStewardLease(name: string, memberDir: string, now: string): Promise<StatusSteward["lease"]> {
|
|
204
|
+
try {
|
|
205
|
+
const { prefix } = await resolveMemberLedger(memberDir);
|
|
206
|
+
const sha = await readRefSha(leaseRef(stewardLeaseName(name), prefix), { cwd: memberDir });
|
|
207
|
+
if (!sha) return null;
|
|
208
|
+
const record = JSON.parse((await readBlobBySha(sha, { cwd: memberDir })) ?? "") as Record<string, unknown>;
|
|
209
|
+
if (typeof record.holder !== "string" || typeof record.expiresAt !== "string" || typeof record.acquiredAt !== "string") return null;
|
|
210
|
+
return {
|
|
211
|
+
holder: record.holder,
|
|
212
|
+
acquiredAt: record.acquiredAt,
|
|
213
|
+
expiresAt: record.expiresAt,
|
|
214
|
+
live: new Date(record.expiresAt).getTime() > new Date(now).getTime(),
|
|
215
|
+
};
|
|
216
|
+
} catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** The work leases `steward`'s turns of `op` hold, from the ledger of the Op's kind or the member's. */
|
|
222
|
+
async function readHeldWorkLeases(steward: string, op: OpConfig, memberDir: string, now: string): Promise<StatusStewardWorkLease[]> {
|
|
223
|
+
try {
|
|
224
|
+
const kind = op.workLease?.kind;
|
|
225
|
+
const cwd = kind ? dirname(resolve(memberDir, kind)) : memberDir;
|
|
226
|
+
const { prefix } = await resolveMemberLedger(cwd);
|
|
227
|
+
const mine = stewardWorkHolder(steward, op.name, "");
|
|
228
|
+
return (await listWorkLeases({ cwd, memberPrefix: prefix, now: new Date(now) }))
|
|
229
|
+
.filter((l) => l.holder.startsWith(mine))
|
|
230
|
+
.map((l) => ({ item: l.item, holder: l.holder, token: l.token, acquiredAt: l.acquiredAt, expiresAt: l.expiresAt, state: l.state }));
|
|
231
|
+
} catch {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The stewards declared in one member, for `env`. `memberDir` is absolute.
|
|
238
|
+
* Only a member of kind `chant` with a config of its own is read.
|
|
239
|
+
*/
|
|
240
|
+
export async function readMemberStewards(
|
|
241
|
+
memberDir: string,
|
|
242
|
+
env: string,
|
|
243
|
+
now: string,
|
|
244
|
+
kind = "chant",
|
|
245
|
+
box: { capabilities: { name: string; broker: string | null }[] } | null = null,
|
|
246
|
+
): Promise<MemberStewards> {
|
|
247
|
+
const reasons: MemberStewards["reasons"] = [];
|
|
248
|
+
if (kind !== "chant" || !isChantProject(memberDir)) return { stewards: [], reasons };
|
|
249
|
+
|
|
250
|
+
let discovered: Awaited<ReturnType<typeof discoverStewards>>;
|
|
251
|
+
try {
|
|
252
|
+
discovered = await discoverStewards({ cwd: memberDir });
|
|
253
|
+
} catch (err) {
|
|
254
|
+
reasons.push({ code: "stewards-unreadable", message: err instanceof Error ? err.message.split("\n")[0] : String(err) });
|
|
255
|
+
return { stewards: [], reasons };
|
|
256
|
+
}
|
|
257
|
+
const { stewards: found, errors, conflicts } = discovered;
|
|
258
|
+
for (const message of errors) reasons.push({ code: "stewards-unreadable", message });
|
|
259
|
+
for (const message of conflicts) reasons.push({ code: "stewards-conflict", message });
|
|
260
|
+
|
|
261
|
+
const stewards: StatusSteward[] = [];
|
|
262
|
+
for (const { declaration, filePath } of [...found.values()].sort((a, b) => a.declaration.name.localeCompare(b.declaration.name))) {
|
|
263
|
+
const ops: StatusStewardOp[] = [];
|
|
264
|
+
const waiting: StatusSteward["waiting"] = [];
|
|
265
|
+
for (const op of declaration.ops) {
|
|
266
|
+
const opEnv = runEnvOf(op);
|
|
267
|
+
let lastRun: StatusStewardRun | null = null;
|
|
268
|
+
try {
|
|
269
|
+
const newest = (await readRunLedger(opEnv, op.name, { cwd: memberDir })).records.at(-1);
|
|
270
|
+
if (newest) {
|
|
271
|
+
lastRun = {
|
|
272
|
+
id: newest.id,
|
|
273
|
+
status: newest.status,
|
|
274
|
+
started: newest.started,
|
|
275
|
+
ended: newest.ended,
|
|
276
|
+
gate: newest.gate ? gateOf(newest.gate, op.name) : null,
|
|
277
|
+
point: newest.point ? waitOf(newest.point) : null,
|
|
278
|
+
};
|
|
279
|
+
if (newest.status === "waiting" && lastRun.point && newest.steward === declaration.name) {
|
|
280
|
+
waiting.push({ op: op.name, run: newest.id, ...lastRun.point });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch (err) {
|
|
284
|
+
reasons.push({
|
|
285
|
+
code: "steward-runs-unreadable",
|
|
286
|
+
message: `${opEnv}/runs__${op.name}.jsonl: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
let lastTick: StatusStewardTick | null = null;
|
|
290
|
+
if (op.labels?.Converge === "true") {
|
|
291
|
+
try {
|
|
292
|
+
const newest = (await readConvergeLedger(opEnv, { cwd: memberDir })).records.filter((r) => r.op === op.name).at(-1);
|
|
293
|
+
if (newest) lastTick = tickOf(newest);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
reasons.push({
|
|
296
|
+
code: "steward-runs-unreadable",
|
|
297
|
+
message: `${opEnv}/converge.jsonl: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
ops.push({
|
|
302
|
+
name: op.name,
|
|
303
|
+
schedule: op.schedule ? { cron: op.schedule.cron, overlap: "skip" } : null,
|
|
304
|
+
env: opEnv,
|
|
305
|
+
lastRun,
|
|
306
|
+
lastTick,
|
|
307
|
+
changesCheckout: op.changesCheckout === true,
|
|
308
|
+
workLease: op.workLease
|
|
309
|
+
? { kind: op.workLease.kind ?? null, held: await readHeldWorkLeases(declaration.name, op, memberDir, now) }
|
|
310
|
+
: null,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
stewards.push({
|
|
314
|
+
name: declaration.name,
|
|
315
|
+
file: relative(realpathSync(memberDir), realpathSync(filePath)).split("\\").join("/"),
|
|
316
|
+
form: stewardFormFor(declaration, env),
|
|
317
|
+
forms: { default: declaration.form.default, environments: { ...declaration.form.environments } },
|
|
318
|
+
vault: typeof declaration.vault === "string" ? declaration.vault : null,
|
|
319
|
+
capabilities: (Array.isArray(declaration.capabilities) ? declaration.capabilities : []).map((name) => {
|
|
320
|
+
const declared = box?.capabilities.find((c) => c.name === name);
|
|
321
|
+
return { name, broker: declared?.broker ?? null, declared: declared !== undefined };
|
|
322
|
+
}),
|
|
323
|
+
lease: await readStewardLease(declaration.name, memberDir, now),
|
|
324
|
+
ops,
|
|
325
|
+
waiting,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return { stewards, reasons };
|
|
329
|
+
}
|