@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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `decide` block in chant.config, the activity's contract, and the step
|
|
3
|
+
* builder (#2828: the systemone lexicon's plugin tests, re-homed in core).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { ChantConfigSchema } from "../config";
|
|
8
|
+
import { decideContract } from "./activities/activity-contracts";
|
|
9
|
+
import * as activities from "./activities";
|
|
10
|
+
import { decide } from "./builders";
|
|
11
|
+
|
|
12
|
+
describe("decide.backends in chant.config", () => {
|
|
13
|
+
const valid = (config: Record<string, unknown>) => ChantConfigSchema.safeParse(config).success;
|
|
14
|
+
|
|
15
|
+
it("accepts env and brokered keys, and a keyless local backend", () => {
|
|
16
|
+
expect(
|
|
17
|
+
valid({
|
|
18
|
+
decide: {
|
|
19
|
+
backends: {
|
|
20
|
+
systemone: { url: "https://api.typesafe.ai", key: { env: "TYPESAFE_API_KEY" } },
|
|
21
|
+
studio: { url: "http://127.0.0.1:7071", key: { capability: "inference", member: "box" }, timeoutMs: 5000 },
|
|
22
|
+
local: { url: "http://127.0.0.1:8080" },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("refuses a literal key, an unknown field, and an unknown key in the block", () => {
|
|
30
|
+
expect(valid({ decide: { backends: { s: { url: "https://x", key: "sk-literal" } } } })).toBe(false);
|
|
31
|
+
expect(valid({ decide: { backends: { s: { url: "https://x", token: { env: "K" } } } } })).toBe(false);
|
|
32
|
+
expect(valid({ decide: { backend: {} } })).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("the decide activity and its contract", () => {
|
|
37
|
+
it("is one of core's activities, with a contract of the same name", () => {
|
|
38
|
+
expect(typeof (activities as Record<string, unknown>).decide).toBe("function");
|
|
39
|
+
expect(decideContract.name).toBe("decide");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("the contract refuses a literal key and an unknown arg", () => {
|
|
43
|
+
expect(decideContract.args.safeParse({ point: "p", backends: { s: { url: "https://x", key: { env: "K" } } } }).success).toBe(true);
|
|
44
|
+
expect(decideContract.args.safeParse({ point: "p", backends: { s: { url: "https://x", key: "sk-literal" } } }).success).toBe(false);
|
|
45
|
+
expect(decideContract.args.safeParse({ point: "p", pont: "typo" }).success).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("the builder makes a fastIdempotent decide step with the point first", () => {
|
|
49
|
+
const step = decide("slice-tier", { read: { "work-item": "W-002" }, subject: "W-002", id: "tier" });
|
|
50
|
+
expect(step).toMatchObject({ kind: "activity", fn: "decide", args: { point: "slice-tier", read: { "work-item": "W-002" }, subject: "W-002" }, profile: "fastIdempotent", id: "tier" });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `decide` block in `chant.config.ts`: the backends a decision point's
|
|
3
|
+
* model decider names, which the `decide` Op activity calls (#2828).
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import type { ChantConfig } from "@intentius/chant/config";
|
|
7
|
+
*
|
|
8
|
+
* export default {
|
|
9
|
+
* decide: {
|
|
10
|
+
* backends: {
|
|
11
|
+
* systemone: { url: "https://api.typesafe.ai", key: { env: "TYPESAFE_API_KEY" } },
|
|
12
|
+
* local: { url: "http://127.0.0.1:8080" },
|
|
13
|
+
* studio: { url: "http://127.0.0.1:7071", key: { capability: "inference", member: "box" } },
|
|
14
|
+
* },
|
|
15
|
+
* },
|
|
16
|
+
* } satisfies ChantConfig;
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* A point's model decider says `"backend": "systemone"`, and that name is the
|
|
20
|
+
* key here. A backend is an endpoint that speaks the `POST /v1/systemone` wire
|
|
21
|
+
* format (TypeSafe's, or any server that implements it, #2491) and where its
|
|
22
|
+
* key comes from.
|
|
23
|
+
*
|
|
24
|
+
* The key is never a literal. It is either an environment variable's name, or
|
|
25
|
+
* a capability the workspace declares as brokered on a box member (#2726).
|
|
26
|
+
* With a brokered capability the broker holds the credential: it either sets
|
|
27
|
+
* the variable `env` names for this process, or it is itself the endpoint and
|
|
28
|
+
* adds the key on the way through, in which case no key is sent from here.
|
|
29
|
+
* SYS001 refuses a string literal where a key goes.
|
|
30
|
+
*
|
|
31
|
+
* This module holds only the schemas, so `../config.ts` can validate the
|
|
32
|
+
* block without loading the client or anything under `workspace/`.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
|
|
37
|
+
/** A key read from an environment variable at call time. */
|
|
38
|
+
export const envKeySchema = z.strictObject({
|
|
39
|
+
/** The variable's name. */
|
|
40
|
+
env: z.string().min(1),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A key reached through a capability a box member declares as brokered
|
|
45
|
+
* (`box.capabilities` in `chant.workspace.json`, #2726). The capability must be
|
|
46
|
+
* declared and name a broker, or the call is refused.
|
|
47
|
+
*/
|
|
48
|
+
export const brokeredKeySchema = z.strictObject({
|
|
49
|
+
/** The capability's name in the box block, such as `inference`. */
|
|
50
|
+
capability: z.string().min(1),
|
|
51
|
+
/** The member whose box declares it. Without it, the one member that declares the capability. */
|
|
52
|
+
member: z.string().min(1).optional(),
|
|
53
|
+
/**
|
|
54
|
+
* The environment variable the broker sets with the scoped key. Without it,
|
|
55
|
+
* the broker is the endpoint and adds the key itself, so none is sent.
|
|
56
|
+
*/
|
|
57
|
+
env: z.string().min(1).optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const keySchema = z.union([envKeySchema, brokeredKeySchema]);
|
|
61
|
+
|
|
62
|
+
export const backendSchema = z.strictObject({
|
|
63
|
+
/** The server's base URL. `/v1/systemone` is appended. */
|
|
64
|
+
url: z.string().min(1),
|
|
65
|
+
/** Where the bearer key comes from. Omitted for a server that takes none, such as a local one. */
|
|
66
|
+
key: keySchema.optional(),
|
|
67
|
+
/** How long to wait for an answer before the backend counts as unreachable, in milliseconds. Default 30000. */
|
|
68
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export const decideConfigSchema = z.strictObject({
|
|
72
|
+
/** Backend name, as a point's model decider names it, to the backend. */
|
|
73
|
+
backends: z.record(z.string(), backendSchema).optional(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export type EnvKey = z.infer<typeof envKeySchema>;
|
|
77
|
+
export type BrokeredKey = z.infer<typeof brokeredKeySchema>;
|
|
78
|
+
export type BackendKey = z.infer<typeof keySchema>;
|
|
79
|
+
export type DecideBackend = z.infer<typeof backendSchema>;
|
|
80
|
+
export type DecideConfig = z.infer<typeof decideConfigSchema>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A point's inputs, read through the read contract.
|
|
3
|
+
*
|
|
4
|
+
* A point names each input as a read-contract output, optionally with a
|
|
5
|
+
* dotted field: `work-item.criteria` is the `criteria` field of a work item as
|
|
6
|
+
* `records --json` lists it (#2738). The decide activity takes, for each
|
|
7
|
+
* output, what to read (`read: { "work-item": "W-002" }`), reads it the way
|
|
8
|
+
* the read contract does, and gives each input its field. Values passed in
|
|
9
|
+
* `inputs` are taken as given and win over read ones.
|
|
10
|
+
*
|
|
11
|
+
* Read by id or name here:
|
|
12
|
+
*
|
|
13
|
+
* record, decision, work-item a record, by id, from the declared record kinds (`records --json`)
|
|
14
|
+
* member a member, by name (`ls --json`)
|
|
15
|
+
*
|
|
16
|
+
* The other outputs a point may name (finding, region, commit, gate, release,
|
|
17
|
+
* environment, component) are passed in `inputs`, as the output printed them.
|
|
18
|
+
* These reads never call a model, and never write. The module loads code
|
|
19
|
+
* under `workspace/`, so the `decide` activity imports it on first call.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { declaredKindFiles, queryDeclaredRecords } from "../workspace/records-cli";
|
|
23
|
+
import { listWorkspace } from "../workspace/ls";
|
|
24
|
+
import { inputOutput } from "../workspace/points";
|
|
25
|
+
|
|
26
|
+
/** The outputs {@link readInputs} reads by id or name. */
|
|
27
|
+
export const READABLE_OUTPUTS = ["record", "decision", "work-item", "member"] as const;
|
|
28
|
+
|
|
29
|
+
/** One output a decide step asked to read that could not be read. */
|
|
30
|
+
export class InputReadError extends Error {
|
|
31
|
+
constructor(message: string) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "InputReadError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A field of `value` by dotted path, or undefined. */
|
|
38
|
+
export function fieldOf(value: unknown, path: string): unknown {
|
|
39
|
+
let at: unknown = value;
|
|
40
|
+
for (const part of path.split(".")) {
|
|
41
|
+
if (at === null || typeof at !== "object" || !Object.prototype.hasOwnProperty.call(at, part)) return undefined;
|
|
42
|
+
at = (at as Record<string, unknown>)[part];
|
|
43
|
+
}
|
|
44
|
+
return at;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function readRecord(id: string, cwd: string, output: string): Promise<Record<string, unknown>> {
|
|
48
|
+
const set = await queryDeclaredRecords(declaredKindFiles(cwd), { cwd });
|
|
49
|
+
const unread: string[] = [];
|
|
50
|
+
for (const doc of set.kinds) {
|
|
51
|
+
if ("error" in doc) {
|
|
52
|
+
unread.push(`${doc.declared.path}: ${doc.error.code}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const found = doc.records.find((r) => r.id === id);
|
|
56
|
+
if (found) return found as unknown as Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
throw new InputReadError(`no declared record kind holds a record with id ${id} (read for ${output})${unread.length ? `; not read: ${unread.join(", ")}` : ""}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readMember(name: string, cwd: string): Record<string, unknown> {
|
|
62
|
+
const doc = listWorkspace({ cwd });
|
|
63
|
+
if ("error" in doc) throw new InputReadError(`the workspace can't be listed: ${doc.error.code}: ${doc.error.message}`);
|
|
64
|
+
const found = doc.members.find((m) => m.name === name);
|
|
65
|
+
if (!found) throw new InputReadError(`the workspace has no member named ${name}`);
|
|
66
|
+
return found as unknown as Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The value of one declared input from what was read for its output. A
|
|
71
|
+
* record's field is looked up in its front matter first, then in the record
|
|
72
|
+
* as `records --json` lists it (`state`, `valid`, `path`...). An input with no
|
|
73
|
+
* field is the whole value.
|
|
74
|
+
*/
|
|
75
|
+
function inputValue(input: string, output: string, read: Record<string, unknown>): unknown {
|
|
76
|
+
const field = input.length > output.length ? input.slice(output.length + 1) : "";
|
|
77
|
+
if (field === "") return read;
|
|
78
|
+
if (output === "member") return fieldOf(read, field);
|
|
79
|
+
const fromData = fieldOf(read.data, field);
|
|
80
|
+
return fromData !== undefined ? fromData : fieldOf(read, field);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ReadInputsResult {
|
|
84
|
+
inputs: Record<string, unknown>;
|
|
85
|
+
/** Declared inputs of a read output that the value read does not have. Left out, so no table row matches on them. */
|
|
86
|
+
missing: string[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Read each output `read` names, and give the point's inputs that name it
|
|
91
|
+
* their values. Throws an {@link InputReadError} for an output this does not
|
|
92
|
+
* read, one the point does not name, or an id that is not there.
|
|
93
|
+
*/
|
|
94
|
+
export async function readInputs(declared: string[], read: Record<string, string>, cwd: string): Promise<ReadInputsResult> {
|
|
95
|
+
const named = new Set(declared.map(inputOutput));
|
|
96
|
+
for (const output of Object.keys(read)) {
|
|
97
|
+
if (!(READABLE_OUTPUTS as readonly string[]).includes(output)) {
|
|
98
|
+
throw new InputReadError(`${output} is not read by id here; pass its values in inputs, as the read contract printed them (read takes ${READABLE_OUTPUTS.join(", ")})`);
|
|
99
|
+
}
|
|
100
|
+
if (!named.has(output)) throw new InputReadError(`the point reads no ${output} input (its inputs are ${declared.join(", ")})`);
|
|
101
|
+
}
|
|
102
|
+
const values = new Map<string, Record<string, unknown>>();
|
|
103
|
+
for (const [output, id] of Object.entries(read)) {
|
|
104
|
+
values.set(output, output === "member" ? readMember(id, cwd) : await readRecord(id, cwd, output));
|
|
105
|
+
}
|
|
106
|
+
const inputs: Record<string, unknown> = {};
|
|
107
|
+
const missing: string[] = [];
|
|
108
|
+
for (const input of declared) {
|
|
109
|
+
const output = inputOutput(input);
|
|
110
|
+
const value = values.get(output);
|
|
111
|
+
if (value === undefined) continue;
|
|
112
|
+
const v = inputValue(input, output, value);
|
|
113
|
+
if (v === undefined) missing.push(input);
|
|
114
|
+
else inputs[input] = v;
|
|
115
|
+
}
|
|
116
|
+
return { inputs, missing };
|
|
117
|
+
}
|
package/src/op/discover.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { OpConfig } from "./types";
|
|
|
5
5
|
import { walkDiscovery, workspaceMemberDirs } from "../discovery/walk";
|
|
6
6
|
import { hasDiscoveryMarkerSync } from "../discovery/files";
|
|
7
7
|
import { resolveDiscoveryGlobs } from "../config";
|
|
8
|
+
import { isStewardDeclaration, type StewardDeclaration } from "./steward";
|
|
8
9
|
|
|
9
10
|
export interface DiscoveredOp {
|
|
10
11
|
config: OpConfig;
|
|
@@ -152,6 +153,9 @@ export async function discoverOps(opts?: { cwd?: string }): Promise<OpDiscoveryR
|
|
|
152
153
|
const mod = (await import(filePath)) as Record<string, unknown>;
|
|
153
154
|
const exported = opsExportedBy(mod);
|
|
154
155
|
|
|
156
|
+
// A file may hold a steward and no Op (#2731): the steward sits beside
|
|
157
|
+
// the Ops it runs, and `discoverStewards` reads it from here.
|
|
158
|
+
if (exported.length === 0 && Object.values(mod).some(isStewardDeclaration)) continue;
|
|
155
159
|
if (exported.length === 0) {
|
|
156
160
|
errors.push(
|
|
157
161
|
`${filePath}: exports no Op. Expected \`export default Op({...})\` or a named export such as \`export const deploy = Op({...})\``,
|
|
@@ -180,3 +184,70 @@ export async function discoverOps(opts?: { cwd?: string }): Promise<OpDiscoveryR
|
|
|
180
184
|
|
|
181
185
|
return { ops, errors };
|
|
182
186
|
}
|
|
187
|
+
|
|
188
|
+
export interface DiscoveredSteward {
|
|
189
|
+
declaration: StewardDeclaration;
|
|
190
|
+
filePath: string;
|
|
191
|
+
exportName: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface StewardDiscoveryResult {
|
|
195
|
+
stewards: Map<string, DiscoveredSteward>;
|
|
196
|
+
/** Files that could not be imported. */
|
|
197
|
+
errors: string[];
|
|
198
|
+
/** Stewards dropped because they would be a second writer: a name or an Op another steward already has. */
|
|
199
|
+
conflicts: string[];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Every steward declared in the project's `*.op.ts` files (#2731), found the
|
|
204
|
+
* way {@link discoverOps} finds Ops, keyed by the steward's name.
|
|
205
|
+
*
|
|
206
|
+
* Refuses, as errors, the two ways a project could end up with two writers:
|
|
207
|
+
* two stewards of one name, and one Op listed by two stewards. The steward
|
|
208
|
+
* declared first (in file walk order) keeps its entry; the other is dropped
|
|
209
|
+
* and reported in `conflicts`.
|
|
210
|
+
*/
|
|
211
|
+
export async function discoverStewards(opts?: { cwd?: string }): Promise<StewardDiscoveryResult> {
|
|
212
|
+
const errors: string[] = [];
|
|
213
|
+
const conflicts: string[] = [];
|
|
214
|
+
const stewards = new Map<string, DiscoveredSteward>();
|
|
215
|
+
const opOwner = new Map<string, string>();
|
|
216
|
+
|
|
217
|
+
const root = await findDiscoveryRoot(opts?.cwd);
|
|
218
|
+
const files = await collectOpFiles(root);
|
|
219
|
+
|
|
220
|
+
for (const filePath of files) {
|
|
221
|
+
let mod: Record<string, unknown>;
|
|
222
|
+
try {
|
|
223
|
+
mod = (await import(filePath)) as Record<string, unknown>;
|
|
224
|
+
} catch (err) {
|
|
225
|
+
errors.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const seen = new Set<unknown>();
|
|
229
|
+
const exportNames = ["default", ...Object.keys(mod).filter((k) => k !== "default")];
|
|
230
|
+
for (const exportName of exportNames) {
|
|
231
|
+
const value = mod[exportName];
|
|
232
|
+
if (!isStewardDeclaration(value) || seen.has(value)) continue;
|
|
233
|
+
seen.add(value);
|
|
234
|
+
const prior = stewards.get(value.name);
|
|
235
|
+
if (prior) {
|
|
236
|
+
conflicts.push(`Duplicate steward "${value.name}" in ${filePath} and ${prior.filePath}`);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const claimed = value.ops.find((op) => opOwner.has(op.name));
|
|
240
|
+
if (claimed) {
|
|
241
|
+
conflicts.push(
|
|
242
|
+
`${filePath}: steward "${value.name}" lists op "${claimed.name}", which steward "${opOwner.get(claimed.name)}" already runs. ` +
|
|
243
|
+
`Two stewards running one Op are two writers; list it on one of them.`,
|
|
244
|
+
);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
for (const op of value.ops) opOwner.set(op.name, value.name);
|
|
248
|
+
stewards.set(value.name, { declaration: value, filePath, exportName });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return { stewards, errors, conflicts };
|
|
253
|
+
}
|
package/src/op/gate-approval.ts
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
import { createHash } from "node:crypto";
|
|
35
|
-
import { lexiconModulePath } from "../lexicon-module";
|
|
35
|
+
import { lexiconModulePath, importLexiconPackage } from "../lexicon-module";
|
|
36
36
|
import { isStepOutputRef, type StepOutputRef } from "./step-output-ref";
|
|
37
37
|
|
|
38
38
|
/** What a policy's decision does to the gate. */
|
|
@@ -226,7 +226,7 @@ export async function loadGatePolicyEvaluator(lexicon: string): Promise<GatePoli
|
|
|
226
226
|
const spec = lexiconModulePath(lexicon) ?? `@intentius/chant-lexicon-${lexicon}/gate-policy`;
|
|
227
227
|
let mod: Partial<GatePolicyEvaluator>;
|
|
228
228
|
try {
|
|
229
|
-
mod = (await
|
|
229
|
+
mod = (await importLexiconPackage(spec)) as Partial<GatePolicyEvaluator>;
|
|
230
230
|
} catch (err) {
|
|
231
231
|
throw new Error(
|
|
232
232
|
`the gate's policy is evaluated by ${spec}, which could not be loaded: ` +
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A step whose command stopped at a gate of its own (#2779).
|
|
3
|
+
*
|
|
4
|
+
* Some chant commands decide a gate themselves rather than through a `gate`
|
|
5
|
+
* step: `chant workspace upgrade` stages its patch, records
|
|
6
|
+
* `workspace-upgrade` / `<scope>` pending on the gate ledger, and exits 3.
|
|
7
|
+
* An Op step that runs such a command stops its run at that gate by throwing
|
|
8
|
+
* {@link GateWait} with the pending fact the command recorded. The executor
|
|
9
|
+
* ends the run there with status `gated`, as it does at a pending `gate`
|
|
10
|
+
* step: no later step runs, no `onFailure` phase runs, a work lease is
|
|
11
|
+
* released `gated`, and the run ledger names the gate, with the op it is
|
|
12
|
+
* recorded under when that isn't the run's own.
|
|
13
|
+
*
|
|
14
|
+
* Nothing waits. After `chant approve <op> <gate>` the next run runs the
|
|
15
|
+
* command again, the command finds the approval, and the step goes on. A
|
|
16
|
+
* local steward re-runs its own gated run on the first round after the
|
|
17
|
+
* approval (`./operator.ts`).
|
|
18
|
+
*
|
|
19
|
+
* `shell()` throws it for an exit code named in `gatedExit`
|
|
20
|
+
* (`./activities/shell.ts`); an activity of a lexicon can throw it too.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
24
|
+
|
|
25
|
+
/** The marker {@link GateWait} carries, so a copy of core linked twice still recognises it. */
|
|
26
|
+
const GATE_WAIT = Symbol.for("chant.op.GateWait");
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Thrown by an activity whose command stopped at a gate. Not a failure: the
|
|
30
|
+
* executor ends the run with status `gated` (see the module doc).
|
|
31
|
+
*/
|
|
32
|
+
export class GateWait extends Error {
|
|
33
|
+
readonly [GATE_WAIT] = true;
|
|
34
|
+
constructor(readonly pending: PendingGateRecord) {
|
|
35
|
+
super(`stopped at gate ${pending.op} / ${pending.gate}: approve it with \`chant approve ${pending.op} ${pending.gate}\` and run again`);
|
|
36
|
+
this.name = "GateWait";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Is this a {@link GateWait}? Duck-typed on the shared symbol. */
|
|
41
|
+
export function isGateWait(err: unknown): err is GateWait {
|
|
42
|
+
return !!err && typeof err === "object" && (err as Record<symbol, unknown>)[GATE_WAIT] === true && "pending" in err;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* `value` as a pending gate fact, or undefined when it isn't one: it needs
|
|
47
|
+
* the `op`, `gate`, `timestamp` and `expiresAt` every pending line on the
|
|
48
|
+
* gate ledger carries.
|
|
49
|
+
*/
|
|
50
|
+
export function asPendingGate(value: unknown): PendingGateRecord | undefined {
|
|
51
|
+
if (!value || typeof value !== "object") return undefined;
|
|
52
|
+
const v = value as Record<string, unknown>;
|
|
53
|
+
for (const key of ["op", "gate", "timestamp", "expiresAt"]) {
|
|
54
|
+
if (typeof v[key] !== "string" || v[key] === "") return undefined;
|
|
55
|
+
}
|
|
56
|
+
return { ...(v as unknown as PendingGateRecord), version: 1, kind: "pending" };
|
|
57
|
+
}
|
package/src/op/gate.ts
CHANGED
|
@@ -481,8 +481,13 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
|
|
|
481
481
|
/**
|
|
482
482
|
* The line every renderer prints to say how a pending gate is cleared. A
|
|
483
483
|
* component gate names its environment (#2574), so the approval picks that
|
|
484
|
-
* environment's pending fact when others stand beside it.
|
|
484
|
+
* environment's pending fact when others stand beside it. A gate bound to a
|
|
485
|
+
* plan (#2300) gets `--plan <digest>` too (#2832), so the line approves the
|
|
486
|
+
* plan the reader was shown and not whatever is pending by the time they run
|
|
487
|
+
* it.
|
|
485
488
|
*/
|
|
486
|
-
export function approveCommand(op: string, gate: string, environment?: string): string {
|
|
487
|
-
return
|
|
489
|
+
export function approveCommand(op: string, gate: string, environment?: string, planDigest?: string): string {
|
|
490
|
+
return `chant approve ${op} ${gate}` +
|
|
491
|
+
(environment === undefined ? "" : ` --env ${environment}`) +
|
|
492
|
+
(planDigest === undefined ? "" : ` --plan ${planDigest}`);
|
|
488
493
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { discoverOps, type DiscoveredOp } from "./discover";
|
|
20
|
-
import { lexiconModulePath } from "../lexicon-module";
|
|
20
|
+
import { lexiconModulePath, importLexiconPackage } from "../lexicon-module";
|
|
21
21
|
import {
|
|
22
22
|
isLexiconPlugin,
|
|
23
23
|
type LexiconPlugin,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
async function loadLexiconPlugin(name: string): Promise<LexiconPlugin | null> {
|
|
38
38
|
let mod: Record<string, unknown>;
|
|
39
39
|
try {
|
|
40
|
-
mod = (await
|
|
40
|
+
mod = (await importLexiconPackage(lexiconModulePath(name) ?? `@intentius/chant-lexicon-${name}`)) as Record<string, unknown>;
|
|
41
41
|
} catch {
|
|
42
42
|
return null;
|
|
43
43
|
}
|
package/src/op/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { Op, phase, activity, gate, effect, build, kubectlApply, helmInstall, helmInstallPinned, waitForStack, waitForReady,
|
|
2
|
-
gitlabPipeline, lifecycleSnapshot, shell, ensureSecret, teardown, envTeardown, k3dUp, k3dDown,
|
|
2
|
+
gitlabPipeline, lifecycleSnapshot, shell, sourceArchive, releasePlan, releaseRecord, releaseRollbackPlan, releaseRollbackRecord, decide, ensureSecret, teardown, envTeardown, k3dUp, k3dDown,
|
|
3
3
|
k3sInstall, k3sUninstall, flociUp, flociDown,
|
|
4
4
|
flociAzUp, flociAzDown, flociGcpUp, flociGcpDown, httpCheck,
|
|
5
5
|
azGroupEnsure, azGroupDelete, azApply, azDelete, awsApply, awsDelete, gcpApply, gcpDelete, policyGate,
|
|
6
|
-
guardValidate,
|
|
6
|
+
guardValidate, workEvidence,
|
|
7
7
|
spriteCreate, spriteExec, spriteCheckpoint, spriteRestore, listCheckpoints, spriteDestroy,
|
|
8
8
|
spriteWriteFile, spriteReadFile, spriteListDir, spriteRemove,
|
|
9
9
|
spriteApplyNetworkPolicy, spriteApplyServices,
|
|
@@ -15,8 +15,12 @@ export { emulatorLifecycle, emulatorsOf, endpointEnvVars } from "./emulator-life
|
|
|
15
15
|
export type { EmulatorSpec, EmulatorCapability, EmulatorDeclaration, EmulatorUpArgs, EmulatorLifecycle } from "./emulator-lifecycle";
|
|
16
16
|
export { checkFreshness, compare, formatResult, latestRelease, parseVersion, unpinned } from "./emulator-freshness";
|
|
17
17
|
export type { FreshnessResult } from "./emulator-freshness";
|
|
18
|
-
export type { OpConfig, OpSchedule, PhaseDefinition, StepDefinition, ActivityStep, GateStep, EffectStep, OutcomeAttribute } from "./types";
|
|
19
|
-
export { outcomeAttributesOf } from "./types";
|
|
18
|
+
export type { OpConfig, OpSchedule, OpWorkLease, PhaseDefinition, StepDefinition, ActivityStep, GateStep, EffectStep, OutcomeAttribute } from "./types";
|
|
19
|
+
export { outcomeAttributesOf, WORK_LEASE_STEP_ID } from "./types";
|
|
20
|
+
export {
|
|
21
|
+
RunWorkLease, workLeaseOutput, stewardWorkHolder, workLeaseProblems, workLeaseNeedsRunItem, LEASE_LOST, WORK_BRANCH_PREFIX,
|
|
22
|
+
} from "./work-lease-run";
|
|
23
|
+
export type { WorkLeaseOutput, WorkLeaseRunResult, WorkClaimOutcome, RunWorkLeaseOptions } from "./work-lease-run";
|
|
20
24
|
export { isValidCronExpression, cronSyntaxMessage, cronMatches, cronDueBetween } from "./cron";
|
|
21
25
|
export {
|
|
22
26
|
WatchOp, ReconcileOp, ApplyOp, ConvergeOp,
|
|
@@ -33,6 +37,10 @@ export type {
|
|
|
33
37
|
LexiconUpgradeOpConfig, LexiconUpgradeOpResources,
|
|
34
38
|
BehaviourOpConfig, BehaviourOpResources,
|
|
35
39
|
} from "./composites";
|
|
40
|
+
export type {
|
|
41
|
+
ConvergeSymptom, ResourceSymptom, ResourceObservation, ObservedResource, ResourceStatus,
|
|
42
|
+
} from "../lifecycle/symptoms";
|
|
43
|
+
export { parseResourceObservation, CONVERGE_RESOURCE_ENV } from "../lifecycle/symptoms";
|
|
36
44
|
export { receiptActivities, receiptCheckInput } from "./receipt-store";
|
|
37
45
|
export type {
|
|
38
46
|
ReceiptStore, EffectReceiptRef, ReceiptCheckInput, ReceiptActivities, ReceiptActivityOptions,
|
|
@@ -47,7 +55,7 @@ export { loadActivities, loadProfiles, resolveActivity } from "./activity-regist
|
|
|
47
55
|
export type { ActivityFn } from "./activity-registry";
|
|
48
56
|
export { loadActivityContracts, mergeActivityContracts } from "./activity-contract-registry";
|
|
49
57
|
export type { LexiconActivityContractContributor } from "./activity-contract-registry";
|
|
50
|
-
export { ACTIVITY_PROFILES, ACTIVITY_PROFILE_NAMES } from "./activity-profiles";
|
|
58
|
+
export { ACTIVITY_PROFILES, ACTIVITY_PROFILE_NAMES, MAX_STEP_TIMEOUT } from "./activity-profiles";
|
|
51
59
|
export type { ActivityProfile, ActivityProfileName } from "./activity-profiles";
|
|
52
60
|
export { NonRetryableActivityError, nonRetryableFailure } from "./activity-failure";
|
|
53
61
|
export { runOpLocally, parseDuration, OpRunFailure } from "./local-executor";
|
|
@@ -100,8 +108,28 @@ export type {
|
|
|
100
108
|
} from "./converge-rule";
|
|
101
109
|
export {
|
|
102
110
|
discoverConvergeOps, runOperatorRound, runOperatorForever, formatRoundLine,
|
|
103
|
-
formatSignalLine, DEFAULT_OPERATOR_INTERVAL_MS,
|
|
111
|
+
formatSignalLine, DEFAULT_OPERATOR_INTERVAL_MS, acquireStewardLease,
|
|
112
|
+
acquireStewardTurn, STEWARD_TURN_WAIT_MS,
|
|
104
113
|
} from "./operator";
|
|
114
|
+
export {
|
|
115
|
+
declareSteward, isStewardDeclaration, stewardFormFor, stewardOpConfig, normaliseStewardForm, stewardLeaseName,
|
|
116
|
+
stewardTurnLeaseName,
|
|
117
|
+
STEWARD_KIND, STEWARD_FORMS, STEWARD_NAME_PATTERN, DEFAULT_STEWARD_ENV,
|
|
118
|
+
} from "./steward";
|
|
119
|
+
export type {
|
|
120
|
+
StewardDeclaration, StewardDeclarationConfig, StewardForm, StewardFormSpec, StewardOpInput,
|
|
121
|
+
} from "./steward";
|
|
122
|
+
export { discoverStewards } from "./discover";
|
|
123
|
+
export {
|
|
124
|
+
askPointInRun, brokeredModelAsk, isPointWait, PointWait, DEFAULT_INFERENCE_CAPABILITY,
|
|
125
|
+
} from "./steward-points";
|
|
126
|
+
export type { AskPointInRunOptions, BrokeredModelAsk, WaitingPoint } from "./steward-points";
|
|
127
|
+
export { GateWait, isGateWait } from "./gate-wait";
|
|
128
|
+
export {
|
|
129
|
+
currentStewardTurn, enterStewardTurn, setStewardTurn, resetStewardTurn, STEWARD_ENV,
|
|
130
|
+
} from "./steward-turn";
|
|
131
|
+
export type { StewardTurn } from "./steward-turn";
|
|
132
|
+
export type { DiscoveredSteward, StewardDiscoveryResult } from "./discover";
|
|
105
133
|
export type {
|
|
106
134
|
OperatorTickEvent, OperatorRoundOptions, OperatorLoopOptions,
|
|
107
135
|
ChangeSubscriber, OperatorSignalEvent,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { withTestDir } from "@intentius/chant-test-utils";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { lifecycleReceiptFile, lifecycleReceiptStore } from "./lifecycle-receipt-store";
|
|
7
|
+
import { receiptActivities, type EffectReceiptRef } from "./receipt-store";
|
|
8
|
+
|
|
9
|
+
function git(args: string[], cwd: string): { stdout: string; exitCode: number } {
|
|
10
|
+
const r = spawnSync("git", args, { cwd, encoding: "utf-8" });
|
|
11
|
+
return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function initRepo(dir: string): void {
|
|
15
|
+
git(["init", "-q", "-b", "main"], dir);
|
|
16
|
+
git(["config", "user.email", "test@chant.dev"], dir);
|
|
17
|
+
git(["config", "user.name", "Test"], dir);
|
|
18
|
+
writeFileSync(join(dir, "README.md"), "fixture\n");
|
|
19
|
+
git(["add", "README.md"], dir);
|
|
20
|
+
git(["commit", "-q", "-m", "init"], dir);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const RECEIPT: EffectReceiptRef = { name: "fly-migration:shop/001_init.sql", effect: "fly-migrate", flavor: "hash", inputs: {} };
|
|
24
|
+
|
|
25
|
+
describe("lifecycleReceiptStore", () => {
|
|
26
|
+
test("a receipt is absent, then written under <env>/receipts on chant/lifecycle", async () => {
|
|
27
|
+
await withTestDir(async (dir) => {
|
|
28
|
+
initRepo(dir);
|
|
29
|
+
const store = lifecycleReceiptStore({ environment: "prod", cwd: dir });
|
|
30
|
+
expect(await store.read(RECEIPT)).toBeUndefined();
|
|
31
|
+
await store.write(RECEIPT, "sha256:abc");
|
|
32
|
+
expect(await store.read(RECEIPT)).toBe("sha256:abc");
|
|
33
|
+
const shown = git(["show", `chant/lifecycle:prod/${lifecycleReceiptFile(RECEIPT.name)}`], dir);
|
|
34
|
+
expect(shown.exitCode).toBe(0);
|
|
35
|
+
expect(shown.stdout.trim()).toBe("sha256:abc");
|
|
36
|
+
// Another environment has its own receipts.
|
|
37
|
+
expect(await lifecycleReceiptStore({ environment: "dev", cwd: dir }).read(RECEIPT)).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("bound to core's receipt activities, the effect step's read-compare-write goes through it", async () => {
|
|
42
|
+
await withTestDir(async (dir) => {
|
|
43
|
+
initRepo(dir);
|
|
44
|
+
const acts = receiptActivities(lifecycleReceiptStore({ environment: "prod", cwd: dir }));
|
|
45
|
+
const first = await acts.receiptRead({ receipt: RECEIPT, expectation: "sha256:1" });
|
|
46
|
+
expect(first).toEqual({ current: null, expectation: "sha256:1", applied: false });
|
|
47
|
+
await acts.receiptWrite({ receipt: RECEIPT, expectation: "sha256:1" });
|
|
48
|
+
expect((await acts.receiptRead({ receipt: RECEIPT, expectation: "sha256:1" })).applied).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("with no environment and no CHANT_ENV it refuses rather than guessing", async () => {
|
|
53
|
+
const store = lifecycleReceiptStore({ env: {} });
|
|
54
|
+
await expect(store.read(RECEIPT)).rejects.toThrow(/no environment/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("receipt names become safe file names", () => {
|
|
58
|
+
expect(lifecycleReceiptFile("fly-migration:app/002 add.sql")).toBe("receipts/fly-migration_app_002_add.sql.receipt");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A receipt store on the `chant/lifecycle` branch (#2736, ws-056).
|
|
3
|
+
*
|
|
4
|
+
* The aws and k8s rows keep receipts in the cloud they deploy to (an SSM
|
|
5
|
+
* parameter, a ConfigMap). A target with no store of its own, such as a Fly
|
|
6
|
+
* Machine, keeps them next to the release ledger instead: one file per receipt
|
|
7
|
+
* at `<env>/receipts/<name>.receipt` on the lifecycle branch, written through
|
|
8
|
+
* the same plumbing as the release and build ledgers (../lifecycle/git.ts), so
|
|
9
|
+
* a workspace member's receipts land under its own `_members/<member>/` prefix.
|
|
10
|
+
*
|
|
11
|
+
* The receipt is per environment, not per checkout: every checkout that
|
|
12
|
+
* fetches the branch sees which effects have fired there. Writing is local;
|
|
13
|
+
* the ledger push that follows a recorded release (or `chant lifecycle push`)
|
|
14
|
+
* carries it to the remote.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readBlobFromPath, writeBlobToPath } from "../lifecycle/git";
|
|
18
|
+
import type { EffectReceiptRef, ReceiptStore } from "./receipt-store";
|
|
19
|
+
|
|
20
|
+
/** Options for {@link lifecycleReceiptStore}. */
|
|
21
|
+
export interface LifecycleReceiptStoreOptions {
|
|
22
|
+
/** The environment whose ledger directory holds the receipts. Omitted, `CHANT_ENV` answers. */
|
|
23
|
+
environment?: string;
|
|
24
|
+
/** The checkout whose lifecycle branch is written. Defaults to the working directory. */
|
|
25
|
+
cwd?: string;
|
|
26
|
+
/** Environment record the `CHANT_ENV` fallback reads. Defaults to `process.env`. */
|
|
27
|
+
env?: Record<string, string | undefined>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The file name a receipt is kept under: its name with anything outside `[A-Za-z0-9_.-]` replaced. */
|
|
31
|
+
export function lifecycleReceiptFile(name: string): string {
|
|
32
|
+
return `receipts/${name.replace(/[^a-zA-Z0-9_.-]/g, "_")}.receipt`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A {@link ReceiptStore} over the lifecycle branch. The environment is resolved
|
|
37
|
+
* at first use, so constructing the store reads nothing; with none given and no
|
|
38
|
+
* `CHANT_ENV`, a read or write fails rather than guessing a directory.
|
|
39
|
+
*/
|
|
40
|
+
export function lifecycleReceiptStore(options: LifecycleReceiptStoreOptions = {}): ReceiptStore {
|
|
41
|
+
const environment = (): string => {
|
|
42
|
+
const env = options.environment ?? (options.env ?? process.env).CHANT_ENV;
|
|
43
|
+
if (!env) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"lifecycle receipt store: no environment. Receipts are kept per environment on chant/lifecycle; " +
|
|
46
|
+
"run with --env <name>, set CHANT_ENV, or pass the environment explicitly.",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return env;
|
|
50
|
+
};
|
|
51
|
+
const opts = options.cwd ? { cwd: options.cwd } : undefined;
|
|
52
|
+
return {
|
|
53
|
+
async read(receipt: EffectReceiptRef): Promise<string | undefined> {
|
|
54
|
+
const content = await readBlobFromPath(environment(), lifecycleReceiptFile(receipt.name), opts);
|
|
55
|
+
return content === null ? undefined : content.trim();
|
|
56
|
+
},
|
|
57
|
+
async write(receipt: EffectReceiptRef, expectation: string): Promise<void> {
|
|
58
|
+
await writeBlobToPath(environment(), lifecycleReceiptFile(receipt.name), `${expectation}\n`, `Receipt ${receipt.name}`, opts);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|