@intentius/chant 0.124.27 → 0.124.28
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/README.md +1 -1
- package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
- package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
- package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
- package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
- package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
- package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
- package/dist/__golden__/wave-loop/op-held-on.json +2 -0
- package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
- package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
- package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
- package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
- package/dist/__golden__/wave-loop/op-per-run.json +7 -0
- package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
- package/dist/__golden__/wave-loop/op-sealed.json +3 -0
- package/dist/__golden__/wave-loop/op-spend.json +3 -0
- package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
- package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
- package/dist/change-set-graph.d.ts +56 -0
- package/dist/change-set-graph.d.ts.map +1 -0
- package/dist/change-set-graph.js +128 -0
- package/dist/change-set.d.ts +52 -3
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +35 -4
- package/dist/chant-version.d.ts +9 -0
- package/dist/chant-version.d.ts.map +1 -0
- package/dist/chant-version.js +16 -0
- package/dist/cli/commands/build.d.ts +8 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +9 -8
- package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.js +30 -2
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.js +13 -2
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.js +17 -8
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +19 -4
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +28 -0
- package/dist/cli/handlers/change-set.d.ts +10 -0
- package/dist/cli/handlers/change-set.d.ts.map +1 -1
- package/dist/cli/handlers/change-set.js +51 -1
- package/dist/cli/handlers/fan-out.d.ts +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +14 -3
- package/dist/cli/handlers/pr.d.ts +8 -2
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +148 -24
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +256 -21
- package/dist/cli/handlers/release.d.ts +34 -0
- package/dist/cli/handlers/release.d.ts.map +1 -0
- package/dist/cli/handlers/release.js +203 -0
- package/dist/cli/handlers/run.d.ts +28 -2
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +170 -13
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +88 -13
- package/dist/cli/mcp/workspace-tools.js +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +49 -3
- package/dist/cli/registry.d.ts +29 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/run-events.d.ts +29 -0
- package/dist/cli/run-events.d.ts.map +1 -0
- package/dist/cli/run-events.js +47 -0
- package/dist/cli/version.d.ts +3 -5
- package/dist/cli/version.d.ts.map +1 -1
- package/dist/cli/version.js +3 -13
- package/dist/codegen/docs-in-site.d.ts +71 -0
- package/dist/codegen/docs-in-site.d.ts.map +1 -0
- package/dist/codegen/docs-in-site.js +177 -0
- package/dist/codegen/docs-types.d.ts +14 -1
- package/dist/codegen/docs-types.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts +18 -1
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/docs.js +40 -7
- package/dist/codegen/rehype-base-url.mjs +15 -9
- package/dist/components/auto-release.d.ts +4 -1
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/auto-release.js +2 -0
- package/dist/components/cli-support.d.ts +6 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +10 -9
- package/dist/components/driver.d.ts +42 -9
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +81 -54
- package/dist/components/fan-out-run.d.ts +7 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-run.js +35 -26
- package/dist/components/pr-run.d.ts +11 -0
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +20 -1
- package/dist/components/promote-order.d.ts +38 -0
- package/dist/components/promote-order.d.ts.map +1 -0
- package/dist/components/promote-order.js +62 -0
- package/dist/components/promote.d.ts +15 -2
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +17 -0
- package/dist/components/release-compile.d.ts +133 -0
- package/dist/components/release-compile.d.ts.map +1 -0
- package/dist/components/release-compile.js +376 -0
- package/dist/components/release-gate.d.ts +125 -0
- package/dist/components/release-gate.d.ts.map +1 -0
- package/dist/components/release-gate.js +282 -0
- package/dist/components/release-promote.d.ts +96 -0
- package/dist/components/release-promote.d.ts.map +1 -0
- package/dist/components/release-promote.js +204 -0
- package/dist/components/run-progress.d.ts +7 -0
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/wave-plan.d.ts +3 -1
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lifecycle/change-set-store.d.ts +67 -0
- package/dist/lifecycle/change-set-store.d.ts.map +1 -0
- package/dist/lifecycle/change-set-store.js +96 -0
- package/dist/lifecycle/commit-sha.d.ts +13 -0
- package/dist/lifecycle/commit-sha.d.ts.map +1 -0
- package/dist/lifecycle/commit-sha.js +21 -0
- package/dist/lifecycle/gate-change-set.d.ts +69 -0
- package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
- package/dist/lifecycle/gate-change-set.js +106 -0
- package/dist/lifecycle/gate-ledger.d.ts +49 -1
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.js +18 -0
- package/dist/lifecycle/release-cut.d.ts +107 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -0
- package/dist/lifecycle/release-cut.js +273 -0
- package/dist/lifecycle/release-ledger.d.ts +54 -0
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.js +53 -20
- package/dist/lifecycle/release-store.d.ts +190 -0
- package/dist/lifecycle/release-store.d.ts.map +1 -0
- package/dist/lifecycle/release-store.js +318 -0
- package/dist/lifecycle/run-ledger.d.ts +4 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +2 -0
- package/dist/op/activities/change-coverage.d.ts.map +1 -1
- package/dist/op/activities/change-coverage.js +4 -2
- package/dist/op/activities/source-release.d.ts.map +1 -1
- package/dist/op/activities/source-release.js +7 -3
- package/dist/op/activities/source-rollback.d.ts.map +1 -1
- package/dist/op/activities/source-rollback.js +7 -3
- package/dist/op/gate-name.d.ts +11 -2
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-name.js +12 -3
- package/dist/op/gate-run-locator.d.ts +22 -0
- package/dist/op/gate-run-locator.d.ts.map +1 -1
- package/dist/op/gate-run-locator.js +31 -0
- package/dist/op/gate.d.ts +27 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +57 -3
- package/dist/op/local-executor.d.ts +36 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +158 -52
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +4 -0
- package/dist/op/run-live.d.ts +2 -0
- package/dist/op/run-live.d.ts.map +1 -1
- package/dist/op/run-live.js +1 -1
- package/dist/op/runtime.d.ts +46 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/runtimes/local.js +8 -0
- package/dist/point-decision.d.ts +2 -0
- package/dist/point-decision.d.ts.map +1 -1
- package/dist/point-decision.js +9 -2
- package/dist/run-event-store.d.ts +77 -0
- package/dist/run-event-store.d.ts.map +1 -0
- package/dist/run-event-store.js +204 -0
- package/dist/run-event-stream.d.ts +132 -0
- package/dist/run-event-stream.d.ts.map +1 -0
- package/dist/run-event-stream.js +238 -0
- package/dist/run-event.d.ts +267 -0
- package/dist/run-event.d.ts.map +1 -0
- package/dist/run-event.js +207 -0
- package/dist/wave-runtime.mjs +204 -199
- package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
- package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/run-event-check.js +59 -0
- package/dist/workspace/change-set-show.d.ts +56 -0
- package/dist/workspace/change-set-show.d.ts.map +1 -0
- package/dist/workspace/change-set-show.js +54 -0
- package/dist/workspace/change-set-show.schema.json +102 -0
- package/dist/workspace/change-set.schema.json +28 -1
- package/dist/workspace/changes-cli.d.ts.map +1 -1
- package/dist/workspace/changes-cli.js +10 -1
- package/dist/workspace/changes.d.ts +37 -0
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/changes.js +49 -2
- package/dist/workspace/changes.schema.json +135 -0
- package/dist/workspace/ci-green-cli.d.ts +19 -4
- package/dist/workspace/ci-green-cli.d.ts.map +1 -1
- package/dist/workspace/ci-green-cli.js +75 -12
- package/dist/workspace/ci-green-forge.d.ts +20 -3
- package/dist/workspace/ci-green-forge.d.ts.map +1 -1
- package/dist/workspace/ci-green-forge.js +6 -4
- package/dist/workspace/ci-green-workflow.d.ts +11 -0
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +6 -0
- package/dist/workspace/ci-green.d.ts +273 -13
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +449 -38
- package/dist/workspace/ci-last-green.schema.json +32 -2
- package/dist/workspace/ci-list.schema.json +525 -0
- package/dist/workspace/commit-links.d.ts +85 -0
- package/dist/workspace/commit-links.d.ts.map +1 -0
- package/dist/workspace/commit-links.js +163 -0
- package/dist/workspace/declaration.d.ts +19 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +1 -1
- package/dist/workspace/declaration.schema.json +12 -0
- package/dist/workspace/identity-gate-rule.d.ts +37 -0
- package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
- package/dist/workspace/identity-gate-rule.js +72 -7
- package/dist/workspace/identity.d.ts +12 -0
- package/dist/workspace/identity.d.ts.map +1 -1
- package/dist/workspace/identity.js +13 -1
- package/dist/workspace/intent.d.ts +6 -1
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +51 -1
- package/dist/workspace/ls.d.ts +4 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +2 -0
- package/dist/workspace/ls.schema.json +3 -1
- package/dist/workspace/member-pipeline.d.ts +6 -0
- package/dist/workspace/member-pipeline.d.ts.map +1 -1
- package/dist/workspace/member-pipeline.js +6 -0
- package/dist/workspace/promotion-order.d.ts +41 -0
- package/dist/workspace/promotion-order.d.ts.map +1 -0
- package/dist/workspace/promotion-order.js +95 -0
- package/dist/workspace/reason-codes.d.ts +2 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +4 -1
- package/dist/workspace/releases.d.ts +72 -0
- package/dist/workspace/releases.d.ts.map +1 -0
- package/dist/workspace/releases.js +94 -0
- package/dist/workspace/releases.schema.json +391 -0
- package/dist/workspace/run-event.schema.json +288 -0
- package/dist/workspace/status-gates.d.ts +26 -1
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-gates.js +31 -2
- package/dist/workspace/status-stewards.d.ts +4 -0
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status-stewards.js +2 -0
- package/dist/workspace/status.d.ts +39 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +48 -1
- package/dist/workspace/status.schema.json +70 -1
- package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
- package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
- package/dist/workspace/trust/ci-tag-statement.js +123 -0
- package/dist/workspace/trust/op-run-statement.d.ts +91 -0
- package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
- package/dist/workspace/trust/op-run-statement.js +143 -0
- package/dist/workspace/trust/rotation.d.ts +18 -2
- package/dist/workspace/trust/rotation.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.js +65 -16
- package/dist/workspace/trust/seal.d.ts +11 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/seal.js +9 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
- package/dist/workspace/trust/ssh-commit.js +10 -10
- package/package.json +1 -1
- package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
- package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
- package/src/__golden__/wave-loop/fan-out-only.json +6 -0
- package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
- package/src/__golden__/wave-loop/op-cost-member.json +3 -0
- package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
- package/src/__golden__/wave-loop/op-held-on.json +2 -0
- package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
- package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
- package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
- package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
- package/src/__golden__/wave-loop/op-per-run.json +7 -0
- package/src/__golden__/wave-loop/op-pr-review.json +7 -0
- package/src/__golden__/wave-loop/op-sealed.json +3 -0
- package/src/__golden__/wave-loop/op-spend.json +3 -0
- package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
- package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
- package/src/change-set-graph.test.ts +72 -0
- package/src/change-set-graph.ts +133 -0
- package/src/change-set.ts +76 -6
- package/src/chant-version.ts +16 -0
- package/src/cli/commands/build.ts +19 -8
- package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
- package/src/cli/commands/check-lexicon-docs.ts +39 -2
- package/src/cli/commands/check-lexicon.ts +14 -2
- package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
- package/src/cli/commands/init-lexicon.test.ts +25 -0
- package/src/cli/commands/init-lexicon.ts +18 -8
- package/src/cli/handlers/approve-release.test.ts +80 -0
- package/src/cli/handlers/approve.ts +18 -2
- package/src/cli/handlers/build.test.ts +52 -0
- package/src/cli/handlers/build.ts +29 -0
- package/src/cli/handlers/change-set.ts +49 -1
- package/src/cli/handlers/fan-out.test.ts +48 -0
- package/src/cli/handlers/fan-out.ts +15 -3
- package/src/cli/handlers/operator.test.ts +37 -0
- package/src/cli/handlers/pr.test.ts +194 -0
- package/src/cli/handlers/pr.ts +168 -34
- package/src/cli/handlers/promote.test.ts +389 -5
- package/src/cli/handlers/promote.ts +279 -20
- package/src/cli/handlers/release-implicit.e2e.test.ts +153 -0
- package/src/cli/handlers/release.e2e.test.ts +85 -0
- package/src/cli/handlers/release.ts +206 -0
- package/src/cli/handlers/run-auto-release.test.ts +71 -0
- package/src/cli/handlers/run-events-cmd.test.ts +92 -0
- package/src/cli/handlers/run.test.ts +30 -0
- package/src/cli/handlers/run.ts +176 -15
- package/src/cli/main.test.ts +7 -0
- package/src/cli/main.ts +88 -13
- package/src/cli/mcp/never-over-mcp.test.ts +2 -0
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/cli/parse-args.ts +37 -3
- package/src/cli/registry.ts +29 -3
- package/src/cli/run-events.ts +54 -0
- package/src/cli/version.ts +3 -13
- package/src/codegen/docs-in-site.test.ts +166 -0
- package/src/codegen/docs-in-site.ts +182 -0
- package/src/codegen/docs-site/AgentPrompt.astro +26 -1
- package/src/codegen/docs-types.ts +14 -1
- package/src/codegen/docs.ts +60 -7
- package/src/codegen/rehype-base-url.d.mts +3 -1
- package/src/codegen/rehype-base-url.mjs +15 -9
- package/src/codegen/rehype-base-url.test.ts +34 -0
- package/src/components/auto-release.test.ts +21 -0
- package/src/components/auto-release.ts +6 -1
- package/src/components/cli-support.test.ts +82 -0
- package/src/components/cli-support.ts +16 -9
- package/src/components/driver.ts +129 -56
- package/src/components/fan-out-run.ts +56 -35
- package/src/components/pr-run.ts +30 -1
- package/src/components/promote-order.test.ts +90 -0
- package/src/components/promote-order.ts +75 -0
- package/src/components/promote.test.ts +10 -1
- package/src/components/promote.ts +26 -1
- package/src/components/release-compile.test.ts +147 -0
- package/src/components/release-compile.ts +445 -0
- package/src/components/release-gate.test.ts +215 -0
- package/src/components/release-gate.ts +341 -0
- package/src/components/release-promote.test.ts +159 -0
- package/src/components/release-promote.ts +232 -0
- package/src/components/run-progress.ts +7 -0
- package/src/components/wave-plan.ts +1 -1
- package/src/config.ts +7 -1
- package/src/index.ts +2 -0
- package/src/lifecycle/change-set-store.test.ts +217 -0
- package/src/lifecycle/change-set-store.ts +112 -0
- package/src/lifecycle/commit-sha.ts +23 -0
- package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
- package/src/lifecycle/gate-change-set.ts +133 -0
- package/src/lifecycle/gate-ledger.test.ts +33 -0
- package/src/lifecycle/gate-ledger.ts +66 -1
- package/src/lifecycle/release-cut-signed.test.ts +180 -0
- package/src/lifecycle/release-cut.test.ts +411 -0
- package/src/lifecycle/release-cut.ts +367 -0
- package/src/lifecycle/release-ledger.test.ts +14 -0
- package/src/lifecycle/release-ledger.ts +85 -17
- package/src/lifecycle/release-store.ts +423 -0
- package/src/lifecycle/run-ledger.test.ts +10 -0
- package/src/lifecycle/run-ledger.ts +6 -0
- package/src/op/activities/change-coverage.ts +4 -1
- package/src/op/activities/source-release.ts +8 -3
- package/src/op/activities/source-rollback.ts +8 -4
- package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
- package/src/op/gate-name.ts +13 -3
- package/src/op/gate-release.test.ts +126 -0
- package/src/op/gate-run-locator.test.ts +110 -0
- package/src/op/gate-run-locator.ts +35 -0
- package/src/op/gate.ts +88 -6
- package/src/op/local-executor.ts +207 -52
- package/src/op/op-waves-run.ts +4 -0
- package/src/op/run-events.test.ts +166 -0
- package/src/op/run-live.ts +1 -1
- package/src/op/runtime.ts +39 -0
- package/src/op/runtimes/local.test.ts +16 -0
- package/src/op/runtimes/local.ts +9 -0
- package/src/point-decision.ts +12 -2
- package/src/run-event-store.test.ts +144 -0
- package/src/run-event-store.ts +213 -0
- package/src/run-event-stream.test.ts +175 -0
- package/src/run-event-stream.ts +306 -0
- package/src/run-event.test.ts +277 -0
- package/src/run-event.ts +451 -0
- package/src/wave-loop-golden.test.ts +9 -0
- package/src/workspace/__fixtures__/run-event-check.ts +58 -0
- package/src/workspace/ancestry-index.test.ts +93 -0
- package/src/workspace/change-set-show.schema.json +102 -0
- package/src/workspace/change-set-show.ts +72 -0
- package/src/workspace/change-set.schema.json +28 -1
- package/src/workspace/changes-cli.ts +10 -1
- package/src/workspace/changes.schema.json +135 -0
- package/src/workspace/changes.test.ts +1 -1
- package/src/workspace/changes.ts +84 -2
- package/src/workspace/ci-green-cli.ts +75 -13
- package/src/workspace/ci-green-forge.ts +24 -4
- package/src/workspace/ci-green-runs.test.ts +242 -0
- package/src/workspace/ci-green-signed.test.ts +302 -0
- package/src/workspace/ci-green-workflow.test.ts +16 -0
- package/src/workspace/ci-green-workflow.ts +12 -0
- package/src/workspace/ci-green.test.ts +131 -2
- package/src/workspace/ci-green.ts +624 -42
- package/src/workspace/ci-last-green.schema.json +32 -2
- package/src/workspace/ci-list.schema.json +525 -0
- package/src/workspace/commit-links.test.ts +279 -0
- package/src/workspace/commit-links.ts +183 -0
- package/src/workspace/declaration.schema.json +12 -0
- package/src/workspace/declaration.ts +22 -2
- package/src/workspace/identity-gate-rule.ts +91 -7
- package/src/workspace/identity.ts +14 -1
- package/src/workspace/intent.ts +48 -1
- package/src/workspace/ls.schema.json +3 -1
- package/src/workspace/ls.ts +6 -0
- package/src/workspace/member-pipeline.test.ts +17 -0
- package/src/workspace/member-pipeline.ts +9 -0
- package/src/workspace/promotion-order.ts +97 -0
- package/src/workspace/read-contract.e2e.test.ts +4 -1
- package/src/workspace/reason-codes.test.ts +2 -1
- package/src/workspace/reason-codes.ts +4 -1
- package/src/workspace/releases.schema.json +391 -0
- package/src/workspace/releases.test.ts +108 -0
- package/src/workspace/releases.ts +122 -0
- package/src/workspace/run-event.schema.json +288 -0
- package/src/workspace/status-contract.test.ts +52 -1
- package/src/workspace/status-gates.ts +56 -1
- package/src/workspace/status-stewards.ts +6 -0
- package/src/workspace/status.schema.json +70 -1
- package/src/workspace/status.ts +80 -1
- package/src/workspace/trust/ci-tag-statement.ts +151 -0
- package/src/workspace/trust/op-run-statement.ts +148 -0
- package/src/workspace/trust/rotation.ts +61 -14
- package/src/workspace/trust/seal.ts +10 -0
- package/src/workspace/trust/ssh-commit.ts +10 -9
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4041: the local executor's run-event stream (`chant run <op>
|
|
3
|
+
* --events-json`). Every line passes the schema; each step's start comes
|
|
4
|
+
* before its settle under the same step id; and the cases an old
|
|
5
|
+
* `StepRecord` could not tell apart come out as `gated`, `skip: "receipt"`
|
|
6
|
+
* and `skip: "not-run"`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test, vi } from "vitest";
|
|
10
|
+
import type { ActivityFn, ActivityProfile } from "./activity-registry";
|
|
11
|
+
import type { OpConfig } from "./types";
|
|
12
|
+
import { OpRunFailure, runOpLocally, type RunOpOptions } from "./local-executor";
|
|
13
|
+
import { memoryGateLedgerPort } from "./gate";
|
|
14
|
+
import { receiptActivities, type ReceiptStore } from "./receipt-store";
|
|
15
|
+
import { EffectReceipt, receiptExpectation } from "../effect-receipt";
|
|
16
|
+
import { effect } from "./builders";
|
|
17
|
+
import type { RunEvent } from "../run-event";
|
|
18
|
+
import { checkRunEventLines, stepKey, stepTrail } from "../workspace/__fixtures__/run-event-check";
|
|
19
|
+
|
|
20
|
+
vi.mock("./gate", async () => {
|
|
21
|
+
const actual = await vi.importActual<typeof import("./gate")>("./gate");
|
|
22
|
+
return { ...actual, gateIsSealed: async () => false };
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const PROFILES: Record<string, ActivityProfile> = { fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } } };
|
|
26
|
+
const NOW = "2026-10-16T12:00:00.000Z";
|
|
27
|
+
const SHA = "0123456789abcdef0123456789abcdef01234567";
|
|
28
|
+
|
|
29
|
+
const op = (partial: Partial<OpConfig>): OpConfig => ({ name: "deploy-api", overview: "", phases: [], ...partial });
|
|
30
|
+
const ok: ActivityFn = async () => ({ done: true });
|
|
31
|
+
|
|
32
|
+
/** Run an Op with its events captured, settling whether it resolves or rejects. */
|
|
33
|
+
async function run(config: OpConfig, activities: Map<string, ActivityFn>, options: RunOpOptions = {}) {
|
|
34
|
+
const lines: string[] = [];
|
|
35
|
+
let status: string;
|
|
36
|
+
try {
|
|
37
|
+
status = (await runOpLocally(config, activities, PROFILES, undefined, {
|
|
38
|
+
gates: memoryGateLedgerPort(),
|
|
39
|
+
now: NOW,
|
|
40
|
+
runId: "local-1",
|
|
41
|
+
commit: SHA,
|
|
42
|
+
runEvents: { write: (l) => lines.push(l) },
|
|
43
|
+
...options,
|
|
44
|
+
})).status;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (!(err instanceof OpRunFailure)) throw err;
|
|
47
|
+
status = err.result.status;
|
|
48
|
+
}
|
|
49
|
+
return { status, events: checkRunEventLines(lines) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("the local runtime's run events", () => {
|
|
53
|
+
test("a run that passes: run, phases and steps, each step started before it settles, named as the run record names it", async () => {
|
|
54
|
+
const { status, events } = await run(
|
|
55
|
+
op({ phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }] }, { name: "Ship", steps: [{ kind: "activity", fn: "push" }] }] }),
|
|
56
|
+
new Map([["make", ok], ["push", ok]]),
|
|
57
|
+
);
|
|
58
|
+
expect(status).toBe("ok");
|
|
59
|
+
// In a CI job the run keeps its own id and names the CI run as its origin, as its run record does (#4020).
|
|
60
|
+
const ci = Boolean(process.env.GITHUB_RUN_ID || process.env.CI_PIPELINE_ID);
|
|
61
|
+
expect(events[0]).toMatchObject({ runId: "local-1", kind: "op", name: "deploy-api", gitSha: SHA, runtime: ci ? "ci" : "local" });
|
|
62
|
+
expect(events[0]!.runOrigin.forge).toBe(process.env.GITHUB_RUN_ID ? "github" : process.env.CI_PIPELINE_ID ? "gitlab" : "local");
|
|
63
|
+
expect(events.map((e) => e.type)).toEqual([
|
|
64
|
+
"run-start",
|
|
65
|
+
"phase-start", "step-start", "step-settle", "phase-settle",
|
|
66
|
+
"phase-start", "step-start", "step-settle", "phase-settle",
|
|
67
|
+
"run-settle",
|
|
68
|
+
]);
|
|
69
|
+
expect(events.at(-1)).toMatchObject({ status: "ok" });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("a step's start is emitted before its settle, with the same step id", async () => {
|
|
73
|
+
const { events } = await run(
|
|
74
|
+
op({ phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }, { kind: "activity", fn: "make" }] }] }),
|
|
75
|
+
new Map([["make", ok]]),
|
|
76
|
+
);
|
|
77
|
+
const steps = events.filter((e): e is Extract<RunEvent, { type: "step-start" | "step-settle" }> => e.type === "step-start" || e.type === "step-settle");
|
|
78
|
+
// Two steps of one phase share a name; the index keeps them apart.
|
|
79
|
+
expect(steps.map((e) => [e.type, stepKey(e)])).toEqual([
|
|
80
|
+
["step-start", JSON.stringify([null, "Build", "make", 0])],
|
|
81
|
+
["step-settle", JSON.stringify([null, "Build", "make", 0])],
|
|
82
|
+
["step-start", JSON.stringify([null, "Build", "make", 1])],
|
|
83
|
+
["step-settle", JSON.stringify([null, "Build", "make", 1])],
|
|
84
|
+
]);
|
|
85
|
+
expect(steps[0]!.seq).toBeLessThan(steps[1]!.seq);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("a parallel phase starts each step before any settles, and tells two steps of one name apart by index", async () => {
|
|
89
|
+
const { events } = await run(
|
|
90
|
+
op({ phases: [{ name: "Fan", parallel: true, steps: [{ kind: "activity", fn: "probe", args: { at: 1 } }, { kind: "activity", fn: "probe", args: { at: 2 } }] }] }),
|
|
91
|
+
new Map([["probe", ok]]),
|
|
92
|
+
);
|
|
93
|
+
expect(stepTrail(events)).toEqual([
|
|
94
|
+
["step-start", "probe"], ["step-start", "probe"], ["step-settle", "probe", "ok"], ["step-settle", "probe", "ok"],
|
|
95
|
+
]);
|
|
96
|
+
expect(events.filter((e) => e.type === "step-settle").map((e) => (e as { index?: number }).index)).toEqual([0, 1]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("an unapproved gate settles gated after gate-waiting; the steps after it settle skipped as not-run", async () => {
|
|
100
|
+
const { status, events } = await run(
|
|
101
|
+
op({ phases: [{ name: "Ship", steps: [{ kind: "activity", fn: "before" }, { kind: "gate", gate: "approve-prod" }, { kind: "activity", fn: "after" }] }] }),
|
|
102
|
+
new Map([["before", ok], ["after", ok]]),
|
|
103
|
+
);
|
|
104
|
+
expect(status).toBe("gated");
|
|
105
|
+
expect(stepTrail(events)).toEqual([
|
|
106
|
+
["step-start", "before"], ["step-settle", "before", "ok"],
|
|
107
|
+
["step-start", "gate:approve-prod"], ["gate-waiting", "gate:approve-prod"], ["step-settle", "gate:approve-prod", "gated"],
|
|
108
|
+
["step-settle", "after", "skipped:not-run"],
|
|
109
|
+
]);
|
|
110
|
+
expect(events.find((e) => e.type === "gate-waiting")).toMatchObject({ op: "deploy-api", gate: "approve-prod", point: null, index: 1 });
|
|
111
|
+
expect(events.find((e) => e.type === "phase-settle")).toMatchObject({ status: "gated" });
|
|
112
|
+
expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "gated" });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("an approved gate resolves, naming who approved it", async () => {
|
|
116
|
+
const gates = memoryGateLedgerPort({
|
|
117
|
+
resolutions: [{ version: 1, op: "deploy-api", gate: "approve-prod", resolvedBy: "alex", timestamp: "2026-10-16T11:00:00.000Z" }],
|
|
118
|
+
});
|
|
119
|
+
const { status, events } = await run(op({ phases: [{ name: "Ship", steps: [{ kind: "gate", gate: "approve-prod" }] }] }), new Map(), { gates });
|
|
120
|
+
expect(status).toBe("ok");
|
|
121
|
+
expect(events.find((e) => e.type === "gate-resolved")).toMatchObject({ gate: "approve-prod", resolvedBy: "alex", via: null, index: 0 });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("a failed step fails its phase and the run, with its error, and the rest of its phase is not-run", async () => {
|
|
125
|
+
const boom: ActivityFn = async () => { throw new Error("disk full"); };
|
|
126
|
+
const { status, events } = await run(
|
|
127
|
+
op({ phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }, { kind: "activity", fn: "pack" }] }] }),
|
|
128
|
+
new Map([["make", boom], ["pack", ok]]),
|
|
129
|
+
);
|
|
130
|
+
expect(status).toBe("fail");
|
|
131
|
+
expect(stepTrail(events)).toEqual([["step-start", "make"], ["step-settle", "make", "failed"], ["step-settle", "pack", "skipped:not-run"]]);
|
|
132
|
+
expect(events.find((e) => e.type === "phase-settle")).toMatchObject({ status: "failed" });
|
|
133
|
+
expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "failed", error: "disk full" });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("an effect whose receipt matched settles its steps skipped with skip: receipt", async () => {
|
|
137
|
+
const seeded = EffectReceipt("seeded", { effect: "db-seed", flavor: "hash", inputs: { version: 3 } });
|
|
138
|
+
const values = new Map<string, string>([["seeded", receiptExpectation(seeded)]]);
|
|
139
|
+
const store: ReceiptStore = {
|
|
140
|
+
read: async (r) => values.get(r.name),
|
|
141
|
+
write: async (r, e) => void values.set(r.name, e),
|
|
142
|
+
};
|
|
143
|
+
const { receiptRead, receiptWrite } = receiptActivities(store);
|
|
144
|
+
const { status, events } = await run(
|
|
145
|
+
op({ phases: [{ name: "Seed", steps: [effect(seeded, [{ kind: "activity", fn: "seed" }])] }] }),
|
|
146
|
+
new Map<string, ActivityFn>([["seed", ok], ["receiptRead", receiptRead as unknown as ActivityFn], ["receiptWrite", receiptWrite as unknown as ActivityFn]]),
|
|
147
|
+
);
|
|
148
|
+
expect(status).toBe("ok");
|
|
149
|
+
expect(stepTrail(events)).toEqual([
|
|
150
|
+
["step-start", "receiptRead"], ["step-settle", "receiptRead", "ok"],
|
|
151
|
+
["step-settle", "seed", "skipped:receipt"],
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("a run that throws before its first phase still ends its stream, failed with the reason", async () => {
|
|
156
|
+
const lines: string[] = [];
|
|
157
|
+
await expect(
|
|
158
|
+
runOpLocally(op({ phases: [{ name: "P", parallel: true, steps: [effect(EffectReceipt("r", { effect: "e", flavor: "existence", inputs: {} }), [])] }] }), new Map(), PROFILES, undefined, {
|
|
159
|
+
gates: memoryGateLedgerPort(), now: NOW, commit: SHA, runEvents: { write: (l) => lines.push(l) },
|
|
160
|
+
}),
|
|
161
|
+
).rejects.toThrow(/cannot run in a parallel phase/);
|
|
162
|
+
const events = checkRunEventLines(lines);
|
|
163
|
+
expect(events.map((e) => e.type)).toEqual(["run-start", "run-settle"]);
|
|
164
|
+
expect(events.at(-1)).toMatchObject({ status: "failed", error: expect.stringMatching(/cannot run in a parallel phase/) });
|
|
165
|
+
});
|
|
166
|
+
});
|
package/src/op/run-live.ts
CHANGED
|
@@ -86,7 +86,7 @@ function keyOf(prefix: string, env: string, op: string): string {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/** The checkout's shared git directory (the same for every worktree), or null outside a checkout. */
|
|
89
|
-
async function gitCommonDir(cwd: string): Promise<string | null> {
|
|
89
|
+
export async function gitCommonDir(cwd: string): Promise<string | null> {
|
|
90
90
|
try {
|
|
91
91
|
const { stdout } = await execFileAsync("git", ["rev-parse", "--git-common-dir"], { cwd });
|
|
92
92
|
const dir = stdout.trim();
|
package/src/op/runtime.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type { OpConfig } from "./types";
|
|
|
20
20
|
import type { StepRecord, OpRunResult } from "./local-executor";
|
|
21
21
|
import type { WaitingPoint } from "./steward-points";
|
|
22
22
|
import type { GateResolutionRecord } from "../lifecycle/gate-ledger";
|
|
23
|
+
import type { RunOrigin } from "../lifecycle/release-ledger";
|
|
23
24
|
import type { RunComponentsOptions, RunComponentsResult } from "../components/cli-support";
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -159,6 +160,29 @@ export interface OpRunRecord {
|
|
|
159
160
|
* first phase.
|
|
160
161
|
*/
|
|
161
162
|
resumed?: { gate: string; run: string; checkpoint: string };
|
|
163
|
+
/**
|
|
164
|
+
* The commit the run built from (#4020): the checkout's `HEAD`, else the
|
|
165
|
+
* commit the CI job started for. Gate facts the run writes carry the same
|
|
166
|
+
* commit, so a run and its gate join on it. Absent on a record written
|
|
167
|
+
* before 0.124.28 and when the run knew no commit.
|
|
168
|
+
*/
|
|
169
|
+
gitSha?: string;
|
|
170
|
+
/**
|
|
171
|
+
* The CI run this Op ran in (#4020), as a deploy record's `runOrigin` names
|
|
172
|
+
* its run, so the two name a run the same way. `id` is still the Op's own
|
|
173
|
+
* run id. Absent outside CI and on a record written before 0.124.28.
|
|
174
|
+
*/
|
|
175
|
+
runOrigin?: RunOrigin;
|
|
176
|
+
/**
|
|
177
|
+
* The run's statement (#4044): a DSSE envelope over an in-toto statement
|
|
178
|
+
* (`https://intentius.io/chant/op-run/v1`) a runner key signed at the end
|
|
179
|
+
* of the run, naming the run, its commit and the SHA-256 of this record
|
|
180
|
+
* without the statement (`../workspace/trust/op-run-statement.ts`).
|
|
181
|
+
* `chant ci tick` with `ci.green.source: "runs"` counts only a run that
|
|
182
|
+
* carries one. Absent on a run given no runner key, and on a record
|
|
183
|
+
* written before 0.124.28.
|
|
184
|
+
*/
|
|
185
|
+
statement?: { payloadType: string; payload: string; signatures: { keyid: string; sig: string }[] };
|
|
162
186
|
}
|
|
163
187
|
|
|
164
188
|
/** An {@link OpRunRecord} before the ledger stamps its version and mints an id. */
|
|
@@ -187,6 +211,14 @@ export interface OpRunStartOptions {
|
|
|
187
211
|
profile?: string;
|
|
188
212
|
/** Called once per settled step, so `--progress-json` can stream. */
|
|
189
213
|
progress?: (record: StepRecord) => void;
|
|
214
|
+
/**
|
|
215
|
+
* Where the run's event stream goes (#4041, `--events-json`): one
|
|
216
|
+
* `run-event.schema.json` line at a time, written through the one writer
|
|
217
|
+
* (`createRunEventWriter`). The local runtime writes `runtime: "local"`
|
|
218
|
+
* events (`ci` in a CI job); a hosted runtime writes its own, as fountain
|
|
219
|
+
* writes `runtime: "steward"` ones. A runtime that cannot stream omits it.
|
|
220
|
+
*/
|
|
221
|
+
runEvents?: (line: string) => void;
|
|
190
222
|
/** Aborts in-flight work (Ctrl-C). */
|
|
191
223
|
signal?: AbortSignal;
|
|
192
224
|
/**
|
|
@@ -205,6 +237,13 @@ export interface OpRunStartOptions {
|
|
|
205
237
|
* and the run `chant run status --on fountain` reports carry the same id.
|
|
206
238
|
*/
|
|
207
239
|
runId?: string;
|
|
240
|
+
/**
|
|
241
|
+
* Signs the run's record when it settles (#4044), for `chant run <op>
|
|
242
|
+
* --key` or `CHANT_RUNNER_KEY`: the local runtime stores what it returns
|
|
243
|
+
* on the record as `statement`. A runtime that writes no record here
|
|
244
|
+
* ignores it.
|
|
245
|
+
*/
|
|
246
|
+
signRecord?: (record: OpRunRecord) => NonNullable<OpRunRecord["statement"]>;
|
|
208
247
|
}
|
|
209
248
|
|
|
210
249
|
/** What a caller hands {@link OpRuntimeProvider.resolveGate} besides the resolution. */
|
|
@@ -51,6 +51,7 @@ vi.mock("../../lifecycle/run-ledger", async () => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
const { createLocalOpRuntime } = await import("./local");
|
|
54
|
+
const { checkRunEventLines, stepTrail } = await import("../../workspace/__fixtures__/run-event-check");
|
|
54
55
|
|
|
55
56
|
function op(name: string, steps: unknown[]): OpConfig {
|
|
56
57
|
return {
|
|
@@ -145,6 +146,21 @@ describe("the local op runtime", () => {
|
|
|
145
146
|
expect(seen).toEqual(["ok", "ok"]);
|
|
146
147
|
});
|
|
147
148
|
|
|
149
|
+
test("runEvents gets the run's event stream, named by the handle's run id (#4041)", async () => {
|
|
150
|
+
loadActivitiesMock.mockResolvedValue(new Map([["ok", vi.fn(async () => ({}))]]));
|
|
151
|
+
const runtime = createLocalOpRuntime();
|
|
152
|
+
const lines: string[] = [];
|
|
153
|
+
|
|
154
|
+
const handle = await runtime.start(op("hello", [{ kind: "activity", fn: "ok", args: {} }]), { runEvents: (l) => lines.push(l) });
|
|
155
|
+
await handle.result();
|
|
156
|
+
|
|
157
|
+
const events = checkRunEventLines(lines);
|
|
158
|
+
expect(events[0]).toMatchObject({ type: "run-start", runId: handle.runId, kind: "op", name: "hello" });
|
|
159
|
+
expect(events[0]!.runtime).toBe(process.env.GITHUB_RUN_ID || process.env.CI_PIPELINE_ID ? "ci" : "local");
|
|
160
|
+
expect(stepTrail(events)).toEqual([["step-start", "ok"], ["step-settle", "ok", "ok"]]);
|
|
161
|
+
expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "ok" });
|
|
162
|
+
});
|
|
163
|
+
|
|
148
164
|
test("a gated Op settles as gated, naming the gate, and runs nothing after it (#2119)", async () => {
|
|
149
165
|
const after = vi.fn(async () => ({}));
|
|
150
166
|
loadActivitiesMock.mockResolvedValue(new Map([["after", after]]));
|
package/src/op/runtimes/local.ts
CHANGED
|
@@ -176,6 +176,15 @@ export function createLocalOpRuntime(opts: { projectPath?: string } = {}): OpRun
|
|
|
176
176
|
`appended to the run ledger: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
177
177
|
),
|
|
178
178
|
...(startOpts.progress ? { onRecord: startOpts.progress } : {}),
|
|
179
|
+
...(startOpts.runEvents ? { runEvents: { write: startOpts.runEvents } } : {}),
|
|
180
|
+
// #4044: a runner key signs the record as the run settles.
|
|
181
|
+
...(startOpts.signRecord
|
|
182
|
+
? {
|
|
183
|
+
signRecord: startOpts.signRecord,
|
|
184
|
+
onSignError: (err: unknown) =>
|
|
185
|
+
process.stderr.write(`warning: run "${runId}" of "${op.name}" was not signed: ${err instanceof Error ? err.message : String(err)}\n`),
|
|
186
|
+
}
|
|
187
|
+
: {}),
|
|
179
188
|
// #3956: a running step's output, and a still-running line while
|
|
180
189
|
// it is quiet, on stderr as they happen; stdout stays the
|
|
181
190
|
// `--json` record's and `--progress-json`'s.
|
package/src/point-decision.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { parseDuration } from "./op/duration";
|
|
|
36
36
|
import type { GateLedgerPort } from "./op/gate";
|
|
37
37
|
import { DEFAULT_GATE_POINT_PASS, defaultGatePointStore, gatePointInputs, gateSubject, type GatePoint, type GatePointFacts, type GatePointStore } from "./op/gate-point";
|
|
38
38
|
import type { WaitingPoint } from "./op/steward-points";
|
|
39
|
-
import { resolveGateRunLocator, type GateRunLocator } from "./op/gate-run-locator";
|
|
39
|
+
import { ciCommitFromEnv, ciRunIdFromEnv, resolveGateRunLocator, type GateRunLocator } from "./op/gate-run-locator";
|
|
40
40
|
import { refuseGateWithoutRecording } from "./lifecycle/no-record";
|
|
41
41
|
|
|
42
42
|
export {
|
|
@@ -102,6 +102,13 @@ export interface GatePointAsker {
|
|
|
102
102
|
ask(request: GatePointRequest): Promise<GatePointQuestion>;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/** The run and commit a gate record names (#4020): the input's own, else the CI job's. */
|
|
106
|
+
function runStamp(input: { runId?: string; gitSha?: string }): { runId?: string; gitSha?: string } {
|
|
107
|
+
const runId = input.runId || ciRunIdFromEnv();
|
|
108
|
+
const gitSha = input.gitSha || ciCommitFromEnv();
|
|
109
|
+
return { ...(runId ? { runId } : {}), ...(gitSha ? { gitSha } : {}) };
|
|
110
|
+
}
|
|
111
|
+
|
|
105
112
|
/** What the run needs to decide a gate that asks a point. */
|
|
106
113
|
export interface PointGateInput {
|
|
107
114
|
op: string;
|
|
@@ -112,6 +119,8 @@ export interface PointGateInput {
|
|
|
112
119
|
description?: string;
|
|
113
120
|
timeout?: string;
|
|
114
121
|
runId?: string;
|
|
122
|
+
/** The commit the run is at (#4020). Default: the commit the CI job started for, when it runs in CI. */
|
|
123
|
+
gitSha?: string;
|
|
115
124
|
planDigest?: string;
|
|
116
125
|
/** The environment the run was started for: the `gate.env` input. Op gate facts on the ledger record none. */
|
|
117
126
|
env?: string;
|
|
@@ -202,6 +211,7 @@ export async function evaluatePointGate(port: PointGateLedgerPort, asker: GatePo
|
|
|
202
211
|
resolvedBy: resolvedByOf(q),
|
|
203
212
|
timestamp: now,
|
|
204
213
|
note: `answered through decision point ${q.point} (${q.id})`,
|
|
214
|
+
...runStamp(input),
|
|
205
215
|
...(input.planDigest !== undefined ? { planDigest: input.planDigest } : {}),
|
|
206
216
|
approver: { kind: q.decider === "quorum" || q.answeredBy.length > 0 ? "human" : "agent" },
|
|
207
217
|
answer,
|
|
@@ -228,7 +238,7 @@ export async function evaluatePointGate(port: PointGateLedgerPort, asker: GatePo
|
|
|
228
238
|
timestamp: now,
|
|
229
239
|
expiresAt: new Date(new Date(now).getTime() + parseDuration(input.timeout ?? DEFAULT_GATE_EXPIRY)).toISOString(),
|
|
230
240
|
...(input.description ? { description: input.description } : {}),
|
|
231
|
-
...(input
|
|
241
|
+
...runStamp(input),
|
|
232
242
|
...(url ? { url } : {}),
|
|
233
243
|
...(input.planDigest !== undefined ? { planDigest: input.planDigest } : {}),
|
|
234
244
|
...(resume ? { resume } : {}),
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4042: a run's events kept on this host, replayed, and followed while the
|
|
3
|
+
* run is in progress. No servers: a temp git checkout, the real executor and
|
|
4
|
+
* the real writer, and a short poll.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync, appendFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
12
|
+
import { runOpLocally } from "./op/local-executor";
|
|
13
|
+
import { memoryGateLedgerPort } from "./op/gate";
|
|
14
|
+
import type { ActivityFn } from "./op/activity-registry";
|
|
15
|
+
import type { OpConfig } from "./op/types";
|
|
16
|
+
import type { RunEvent } from "./run-event";
|
|
17
|
+
import { openRunEventStream, runEventRunOf } from "./run-event-stream";
|
|
18
|
+
import { findStoredRun, listStoredRuns, openRunEventStore, readRunEvents, runEventsDir } from "./run-event-store";
|
|
19
|
+
import { checkRunEventLines } from "./workspace/__fixtures__/run-event-check";
|
|
20
|
+
|
|
21
|
+
const dirs: string[] = [];
|
|
22
|
+
// These tests read the store a temp checkout keeps under its own git directory.
|
|
23
|
+
beforeEach(() => void vi.stubEnv("CHANT_RUN_EVENTS_DIR", ""));
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.unstubAllEnvs();
|
|
26
|
+
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function checkout(): string {
|
|
30
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "run-event-store-")));
|
|
31
|
+
dirs.push(dir);
|
|
32
|
+
execFileSync("git", ["init", "-q", "-b", "main"], { cwd: dir });
|
|
33
|
+
execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "commit", "-q", "--allow-empty", "-m", "init"], { cwd: dir });
|
|
34
|
+
return dir;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const op: OpConfig = {
|
|
38
|
+
name: "deploy-api",
|
|
39
|
+
overview: "",
|
|
40
|
+
phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }] }],
|
|
41
|
+
};
|
|
42
|
+
const ok: ActivityFn = async () => ({ done: true });
|
|
43
|
+
const PROFILES = { fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } } };
|
|
44
|
+
|
|
45
|
+
describe("a settled run's events", () => {
|
|
46
|
+
test("a run that records to the ledger keeps its stream, and it replays as the writer wrote it", async () => {
|
|
47
|
+
const dir = checkout();
|
|
48
|
+
await runOpLocally(op, new Map([["make", ok]]), PROFILES, undefined, {
|
|
49
|
+
runId: "r-settled",
|
|
50
|
+
ledger: { cwd: dir },
|
|
51
|
+
cwd: dir,
|
|
52
|
+
gates: memoryGateLedgerPort(),
|
|
53
|
+
});
|
|
54
|
+
const stored = (await findStoredRun(dir, "r-settled"))!;
|
|
55
|
+
expect(stored.file.startsWith(join(dir, ".git", "chant", "run-events"))).toBe(true);
|
|
56
|
+
expect(stored.name).toBe("deploy-api");
|
|
57
|
+
|
|
58
|
+
const lines: string[] = [];
|
|
59
|
+
const settle = await readRunEvents(stored.file, (_e, line) => lines.push(line));
|
|
60
|
+
const events = checkRunEventLines(lines);
|
|
61
|
+
expect(settle?.type).toBe("run-settle");
|
|
62
|
+
expect(events.map((e) => e.type)).toContain("step-settle");
|
|
63
|
+
expect(lines.join("")).toBe(readFileSync(stored.file, "utf8"));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("an Op name stands for its newest run", async () => {
|
|
67
|
+
const dir = checkout();
|
|
68
|
+
for (const runId of ["r-1", "r-2"]) {
|
|
69
|
+
await runOpLocally(op, new Map([["make", ok]]), PROFILES, undefined, { runId, ledger: { cwd: dir }, cwd: dir, gates: memoryGateLedgerPort() });
|
|
70
|
+
await new Promise((r) => setTimeout(r, 15));
|
|
71
|
+
}
|
|
72
|
+
expect((await findStoredRun(dir, "deploy-api"))?.runId).toBe("r-2");
|
|
73
|
+
expect((await listStoredRuns(dir)).map((r) => r.runId)).toEqual(["r-2", "r-1"]);
|
|
74
|
+
expect(await findStoredRun(dir, "nothing")).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("a run with no ledger (--no-record) keeps nothing", async () => {
|
|
78
|
+
const dir = checkout();
|
|
79
|
+
await runOpLocally(op, new Map([["make", ok]]), PROFILES, undefined, { runId: "r-none", cwd: dir, gates: memoryGateLedgerPort() });
|
|
80
|
+
const events = await runEventsDir(dir);
|
|
81
|
+
expect(events === null || !existsSync(events)).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("a run in progress", () => {
|
|
86
|
+
async function inFlight() {
|
|
87
|
+
const dir = checkout();
|
|
88
|
+
const store = (await openRunEventStore(dir, "r-live"))!;
|
|
89
|
+
const stream = openRunEventStream(runEventRunOf({ kind: "op", name: "deploy-api", runId: "r-live" }), store.write);
|
|
90
|
+
return { dir, store, stream };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
test("replays what has been written so far, and not a half-written last line", async () => {
|
|
94
|
+
const { store, stream } = await inFlight();
|
|
95
|
+
stream.emit({ type: "run-start", waves: null });
|
|
96
|
+
stream.emit({ type: "phase-start", component: null, phase: "Build" });
|
|
97
|
+
appendFileSync(store.path, '{"type":"step-st');
|
|
98
|
+
const seen: RunEvent[] = [];
|
|
99
|
+
const settle = await readRunEvents(store.path, (e) => seen.push(e));
|
|
100
|
+
expect(settle).toBeUndefined();
|
|
101
|
+
expect(seen.map((e) => e.type)).toEqual(["run-start", "phase-start"]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("--follow streams each event as it is written and ends at run-settle", async () => {
|
|
105
|
+
const { store, stream } = await inFlight();
|
|
106
|
+
stream.emit({ type: "run-start", waves: null });
|
|
107
|
+
const seen: string[] = [];
|
|
108
|
+
const follow = readRunEvents(store.path, (e) => seen.push(`${e.seq}:${e.type}`), { follow: true, pollMs: 5, idleMs: 3000 });
|
|
109
|
+
|
|
110
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
111
|
+
expect(seen).toEqual(["0:run-start"]);
|
|
112
|
+
stream.emit({ type: "phase-start", component: null, phase: "Build" });
|
|
113
|
+
stream.emit({ type: "step-start", component: null, phase: "Build", step: "make" });
|
|
114
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
115
|
+
expect(seen).toEqual(["0:run-start", "1:phase-start", "2:step-start"]);
|
|
116
|
+
stream.settle("ok");
|
|
117
|
+
|
|
118
|
+
const settle = await follow;
|
|
119
|
+
expect(settle?.type).toBe("run-settle");
|
|
120
|
+
expect(seen.at(-1)).toBe("3:run-settle");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("--follow waits for a file that is not there yet", async () => {
|
|
124
|
+
const dir = checkout();
|
|
125
|
+
const file = join((await runEventsDir(dir))!, "later.jsonl");
|
|
126
|
+
const seen: string[] = [];
|
|
127
|
+
const follow = readRunEvents(file, (e) => seen.push(e.type), { follow: true, pollMs: 5, idleMs: 3000 });
|
|
128
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
129
|
+
const store = (await openRunEventStore(dir, "later"))!;
|
|
130
|
+
const stream = openRunEventStream(runEventRunOf({ kind: "op", name: "x", runId: "later" }), store.write);
|
|
131
|
+
stream.settle("failed", "boom");
|
|
132
|
+
expect((await follow)?.type).toBe("run-settle");
|
|
133
|
+
expect(seen).toEqual(["run-start", "run-settle"]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("an aborted follow returns without a verdict", async () => {
|
|
137
|
+
const { store, stream } = await inFlight();
|
|
138
|
+
stream.emit({ type: "run-start", waves: null });
|
|
139
|
+
const ac = new AbortController();
|
|
140
|
+
const follow = readRunEvents(store.path, () => {}, { follow: true, pollMs: 5, signal: ac.signal });
|
|
141
|
+
setTimeout(() => ac.abort(), 20);
|
|
142
|
+
expect(await follow).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
});
|