@intentius/chant 0.124.26 → 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/activity-registry.d.ts +6 -3
- package/dist/op/activity-registry.d.ts.map +1 -1
- package/dist/op/activity-registry.js +7 -5
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +14 -7
- 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 +12 -2
- 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 +225 -242
- 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/compiled-cli.e2e.test.ts +31 -6
- 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/activity-registry.ts +7 -5
- package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
- package/src/op/discover-root.test.ts +39 -0
- package/src/op/discover.ts +15 -8
- 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 +13 -2
- 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,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant components pr-plan` and `pr-apply` with `--events-json` (#4041,
|
|
3
|
+
* #4007): the planning as a `plan` phase, the gate's decision as a `gate`
|
|
4
|
+
* phase, and on pr-apply the waves and components of the apply, all on one
|
|
5
|
+
* schema-valid stream. Git, the change signal, the derivation and the gate
|
|
6
|
+
* ledger are fakes; the planning and the apply are the real ones.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
13
|
+
import type { DriverComponent } from "../../components/driver";
|
|
14
|
+
import type { DeployContext } from "../../components/capability";
|
|
15
|
+
import type { ParsedArgs } from "../registry";
|
|
16
|
+
|
|
17
|
+
const HEAD = "a".repeat(40);
|
|
18
|
+
const BASE = "b".repeat(40);
|
|
19
|
+
|
|
20
|
+
vi.mock("../../components/changed-files", () => ({
|
|
21
|
+
resolveCommit: async () => HEAD,
|
|
22
|
+
resolveMergeBase: async () => BASE,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock("../../config", async () => {
|
|
26
|
+
const actual = await vi.importActual<typeof import("../../config")>("../../config");
|
|
27
|
+
return { ...actual, loadChantConfig: async () => ({ config: {} }) };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
vi.mock("./fan-out", () => ({
|
|
31
|
+
resolveChangedUnits: async () => ({ units: { changed: ["net"], indeterminate: [] } }),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// #4022: pr-apply stores the change set its gate binds on chant/lifecycle and
|
|
35
|
+
// pushes it. These tests run in the chant checkout, so the store is replaced:
|
|
36
|
+
// it must never write or push the real repository's lifecycle branch.
|
|
37
|
+
vi.mock("../../lifecycle/gate-change-set", () => ({
|
|
38
|
+
storeGateChangeSet: async () => ({ written: false }),
|
|
39
|
+
readGraphIds: async () => ({ ids: [] }),
|
|
40
|
+
setGraphIdReader: () => {},
|
|
41
|
+
gateGraphReader: () => async () => ({ ids: [] }),
|
|
42
|
+
}));
|
|
43
|
+
vi.mock("../../lifecycle/git", async () => {
|
|
44
|
+
const actual = await vi.importActual<typeof import("../../lifecycle/git")>("../../lifecycle/git");
|
|
45
|
+
return { ...actual, pushLifecycleReport: async () => { throw new Error("pr.test must not push chant/lifecycle"); } };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
vi.mock("./run", () => ({
|
|
49
|
+
GATED_EXIT_CODE: 3,
|
|
50
|
+
autoReleaseRun: (id: string) => ({ runId: id, runOrigin: { forge: "local" } }),
|
|
51
|
+
recordAutoReleasesForRun: async () => [],
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
let gateLedger: ReturnType<typeof import("../../op/gate").memoryGateLedgerPort>;
|
|
55
|
+
vi.mock("../../op/gate", async () => {
|
|
56
|
+
const actual = await vi.importActual<typeof import("../../op/gate")>("../../op/gate");
|
|
57
|
+
return { ...actual, gitGateLedgerPort: () => gateLedger, gateIsSealed: async () => false };
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const failing = new Set<string>();
|
|
61
|
+
vi.mock("../../components/fan-out-support", async () => {
|
|
62
|
+
const { CapabilityRegistry } = await import("../../components/capability");
|
|
63
|
+
const { planFanOut } = await import("../../components/fan-out");
|
|
64
|
+
const { computePlanDigest } = await import("../../lifecycle/plan-digest");
|
|
65
|
+
const registry = new CapabilityRegistry();
|
|
66
|
+
registry.register({
|
|
67
|
+
kind: "fake-root",
|
|
68
|
+
async plan(_ctx: DeployContext, input: { root: string }) {
|
|
69
|
+
const planDigest = computePlanDigest("fake-root", { root: input.root });
|
|
70
|
+
return {
|
|
71
|
+
member: input.root,
|
|
72
|
+
planDigest,
|
|
73
|
+
artifact: { planDigest },
|
|
74
|
+
changeSet: { member: { member: input.root, lexicon: "terraform", planner: "tofu", status: "planned", planDigest, holes: [] }, entries: [] },
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
async outputs() {
|
|
78
|
+
return {};
|
|
79
|
+
},
|
|
80
|
+
async run(_ctx: DeployContext, input: { root: string }) {
|
|
81
|
+
if (failing.has(input.root)) throw new Error(`${input.root} failed`);
|
|
82
|
+
return { outputs: {} };
|
|
83
|
+
},
|
|
84
|
+
} as never);
|
|
85
|
+
return {
|
|
86
|
+
fanOutRegistry: async () => registry,
|
|
87
|
+
deriveFanOut: async () => ({
|
|
88
|
+
success: true,
|
|
89
|
+
components: ESTATE,
|
|
90
|
+
plan: planFanOut({ components: ESTATE, changed: ["net"] }),
|
|
91
|
+
signal: { changed: ["net"], indeterminate: [], unclaimed: [] },
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const root = (name: string, dependsOn: string[] = []): DriverComponent => ({
|
|
97
|
+
name,
|
|
98
|
+
dependsOn,
|
|
99
|
+
deploy: [{ phase: "Apply", steps: [{ kind: "fake-root", root: name }] }],
|
|
100
|
+
});
|
|
101
|
+
const ESTATE: DriverComponent[] = [root("net"), root("app", ["net"])];
|
|
102
|
+
|
|
103
|
+
const { memoryGateLedgerPort } = await import("../../op/gate");
|
|
104
|
+
const { runComponentsPrApply, runComponentsPrPlan } = await import("./pr");
|
|
105
|
+
const { checkRunEventLines, stepTrail } = await import("../../workspace/__fixtures__/run-event-check");
|
|
106
|
+
|
|
107
|
+
let dir: string;
|
|
108
|
+
let out: string[];
|
|
109
|
+
|
|
110
|
+
const ctx = (a: Partial<ParsedArgs>) =>
|
|
111
|
+
({ args: { command: "components", path: "pr-apply", format: "", fix: false, watch: false, verbose: false, help: false, live: false, base: "main", env: "prod", output: dir, eventsJson: true, ...a } as ParsedArgs, plugins: [] }) as never;
|
|
112
|
+
|
|
113
|
+
beforeEach(async () => {
|
|
114
|
+
dir = await mkdtemp(join(tmpdir(), "pr-events-"));
|
|
115
|
+
out = [];
|
|
116
|
+
failing.clear();
|
|
117
|
+
gateLedger = memoryGateLedgerPort();
|
|
118
|
+
vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array) => { out.push(String(chunk)); return true; });
|
|
119
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
120
|
+
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
afterEach(async () => {
|
|
124
|
+
vi.restoreAllMocks();
|
|
125
|
+
await rm(dir, { recursive: true, force: true });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/** The digest pr-plan printed, read back from its report. */
|
|
129
|
+
async function plannedDigest(): Promise<string> {
|
|
130
|
+
const { readFile } = await import("node:fs/promises");
|
|
131
|
+
return JSON.parse(await readFile(join(dir, "pr-plan.json"), "utf8")).digest as string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
describe("pr-plan --events-json", () => {
|
|
135
|
+
test("plans each component in a plan phase, then says where the approval stands; the run settles ok", async () => {
|
|
136
|
+
expect(await runComponentsPrPlan(ctx({ pr: 12 }))).toBe(0);
|
|
137
|
+
const events = checkRunEventLines(out);
|
|
138
|
+
expect(events[0]).toMatchObject({ type: "run-start", kind: "component", name: "pr-plan pr-12", waves: [["net"], ["app"]] });
|
|
139
|
+
expect(stepTrail(events)).toEqual([
|
|
140
|
+
["step-start", "plan"], ["step-settle", "plan", "ok"],
|
|
141
|
+
["step-start", "plan"], ["step-settle", "plan", "ok"],
|
|
142
|
+
["step-start", "gate:pr-apply"], ["gate-waiting", "gate:pr-apply"], ["step-settle", "gate:pr-apply", "gated"],
|
|
143
|
+
]);
|
|
144
|
+
expect(events.filter((e) => e.type === "step-start" && e.phase === "plan").map((e) => (e as { component: string }).component)).toEqual(["net", "app"]);
|
|
145
|
+
// The plan stage reads the ledger and writes nothing to it.
|
|
146
|
+
expect(gateLedger.appended).toEqual([]);
|
|
147
|
+
expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "ok" });
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("pr-apply --events-json", () => {
|
|
152
|
+
test("refused: the gate phase settles gated with the reason, nothing applies, and the run is gated", async () => {
|
|
153
|
+
expect(await runComponentsPrApply(ctx({ pr: 12 }))).toBe(3);
|
|
154
|
+
const events = checkRunEventLines(out);
|
|
155
|
+
expect(events[0]).toMatchObject({ name: "pr-apply pr-12" });
|
|
156
|
+
expect(events.find((e) => e.type === "gate-waiting")).toMatchObject({ op: "pr-12", gate: "pr-apply", message: expect.stringMatching(/No approval stands for this plan/) });
|
|
157
|
+
expect(events.some((e) => e.type === "wave-start")).toBe(false);
|
|
158
|
+
expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "gated" });
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("approved: the gate resolves, then each wave and component applies; a failure settles the run failed", async () => {
|
|
162
|
+
await runComponentsPrPlan(ctx({ pr: 12 }));
|
|
163
|
+
const digest = await plannedDigest();
|
|
164
|
+
gateLedger = memoryGateLedgerPort({
|
|
165
|
+
resolutions: [{ version: 1, kind: "resolution", op: "pr-12", gate: "pr-apply", resolvedBy: "github:alice", timestamp: new Date().toISOString(), planDigest: digest }],
|
|
166
|
+
});
|
|
167
|
+
out = [];
|
|
168
|
+
failing.add("app");
|
|
169
|
+
expect(await runComponentsPrApply(ctx({ pr: 12 }))).toBe(1);
|
|
170
|
+
const events = checkRunEventLines(out);
|
|
171
|
+
expect(events.map((e) => e.type).filter((t) => t !== "step-start" && t !== "step-settle")).toEqual([
|
|
172
|
+
"run-start",
|
|
173
|
+
"phase-start", "phase-settle",
|
|
174
|
+
"phase-start", "gate-resolved", "phase-settle",
|
|
175
|
+
"wave-start", "component-start", "phase-start", "phase-settle", "component-settle", "wave-settle",
|
|
176
|
+
"wave-start", "component-start", "phase-start", "phase-settle", "component-settle", "wave-settle",
|
|
177
|
+
"run-settle",
|
|
178
|
+
]);
|
|
179
|
+
expect(events.find((e) => e.type === "gate-resolved")).toMatchObject({ resolvedBy: "github:alice" });
|
|
180
|
+
expect(events.find((e) => e.type === "component-settle" && e.component === "app")).toMatchObject({ status: "failed" });
|
|
181
|
+
expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "failed" });
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe("--events-json beside --json", () => {
|
|
186
|
+
test("is refused on pr-plan and pr-apply before anything plans, since both print to stdout", async () => {
|
|
187
|
+
const errors: string[] = [];
|
|
188
|
+
vi.spyOn(console, "error").mockImplementation((...m: unknown[]) => { errors.push(m.join(" ")); });
|
|
189
|
+
expect(await runComponentsPrPlan(ctx({ pr: 12, json: true }))).toBe(1);
|
|
190
|
+
expect(await runComponentsPrApply(ctx({ pr: 12, json: true }))).toBe(1);
|
|
191
|
+
expect(errors.filter((e) => /--events-json and --json both stream to stdout/.test(e))).toHaveLength(2);
|
|
192
|
+
expect(out).toEqual([]);
|
|
193
|
+
});
|
|
194
|
+
});
|
package/src/cli/handlers/pr.ts
CHANGED
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
*
|
|
30
30
|
* Every run writes `pr-plan.json` or `pr-apply.json` (`pr-report.schema.json`),
|
|
31
31
|
* `change-set.json` and `pr-note.md` under `--output` (default `.chant/pr`). *
|
|
32
|
+
* `--events-json` (#4041, #4007) streams the stage's run events to stdout,
|
|
33
|
+
* one `run-event.schema.json` line each: the planning as a `plan` phase with
|
|
34
|
+
* a step per component, the gate's decision as a `gate` phase, and on
|
|
35
|
+
* pr-apply the apply's waves and components as `chant run --components`
|
|
36
|
+
* streams them.
|
|
37
|
+
*
|
|
32
38
|
* `--member <name>` (#3465) runs a workspace member's loop: the member's
|
|
33
39
|
* generated pipeline passes it from the member's directory. The gate is
|
|
34
40
|
* recorded under `pr-<n>-<member>`, and the note and the statuses carry the
|
|
@@ -54,7 +60,10 @@ import {
|
|
|
54
60
|
} from "../../components/pr-run";
|
|
55
61
|
import { readFanOutAttempt, writeFanOutAttempt, type FanOutAttempt, type PrApplyRecord } from "../../components/fan-out-record";
|
|
56
62
|
import { samePlanDigest } from "../../lifecycle/plan-digest";
|
|
57
|
-
import { gitGateLedgerPort } from "../../op/gate";
|
|
63
|
+
import { gitGateLedgerPort, type GateCheck } from "../../op/gate";
|
|
64
|
+
import { emitGateStep, gateEventsOf, GATE_PHASE, type RunEventSink, type RunEventStream } from "../../run-event-stream";
|
|
65
|
+
import type { RunEventSettleStatus } from "../../run-event";
|
|
66
|
+
import { openCliRunEvents, refusesEventsWithJson } from "../run-events";
|
|
58
67
|
import {
|
|
59
68
|
describePlanChanged,
|
|
60
69
|
PR_APPLY_GATE,
|
|
@@ -74,7 +83,7 @@ import { resolveCliBuildParams, parseParamFlags } from "../build-params-cli";
|
|
|
74
83
|
import { formatError, formatInfo, formatSuccess, formatWarning } from "../format";
|
|
75
84
|
import { findWorkspaceRoot } from "../../project-root";
|
|
76
85
|
import { resolveChangedUnits } from "./fan-out";
|
|
77
|
-
import { GATED_EXIT_CODE, recordAutoReleasesForRun } from "./run";
|
|
86
|
+
import { GATED_EXIT_CODE, autoReleaseRun, recordAutoReleasesForRun } from "./run";
|
|
78
87
|
import type { CommandContext } from "../registry";
|
|
79
88
|
import type { ComponentChangeSignal, FanOutPlan } from "../../components/fan-out";
|
|
80
89
|
import type { DriverComponent } from "../../components/driver";
|
|
@@ -225,8 +234,32 @@ async function checkMember(projectPath: string, names: string[]): Promise<string
|
|
|
225
234
|
}
|
|
226
235
|
|
|
227
236
|
/** Plan every member the derivation selects. */
|
|
228
|
-
function planAll(p: Prepared): Promise<PrPlanSet> {
|
|
229
|
-
return planPrSet({
|
|
237
|
+
function planAll(p: Prepared, onRunEvent?: RunEventSink): Promise<PrPlanSet> {
|
|
238
|
+
return planPrSet({
|
|
239
|
+
components: p.components,
|
|
240
|
+
plan: p.plan,
|
|
241
|
+
registry: p.registry,
|
|
242
|
+
env: p.env,
|
|
243
|
+
seededOutputs: p.seededOutputs,
|
|
244
|
+
...(onRunEvent ? { onRunEvent } : {}),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* How a stage's report settles its run's events (#4041): a refusal waits on
|
|
250
|
+
* a person, as a gate does, and exits the same way.
|
|
251
|
+
*/
|
|
252
|
+
function settleOf(status: PrReport["status"]): RunEventSettleStatus {
|
|
253
|
+
switch (status) {
|
|
254
|
+
case "nothing":
|
|
255
|
+
case "planned":
|
|
256
|
+
case "applied":
|
|
257
|
+
return "ok";
|
|
258
|
+
case "refused":
|
|
259
|
+
return "gated";
|
|
260
|
+
default:
|
|
261
|
+
return "failed";
|
|
262
|
+
}
|
|
230
263
|
}
|
|
231
264
|
|
|
232
265
|
/** Write the report, the change set and the note; print the note's path. */
|
|
@@ -284,20 +317,50 @@ async function finish(ctx: CommandContext, p: Prepared, report: PrReport): Promi
|
|
|
284
317
|
if (ctx.args.json) console.log(JSON.stringify(report, null, 2));
|
|
285
318
|
}
|
|
286
319
|
|
|
287
|
-
/**
|
|
288
|
-
|
|
320
|
+
/** A stage's stream, held so that a stage that returns or throws before it settles still ends it. */
|
|
321
|
+
interface StageEvents {
|
|
322
|
+
stream?: RunEventStream;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Run a stage, and end its stream as `failed` when the stage did not settle it. */
|
|
326
|
+
async function withStageEvents(stage: string, run: (events: StageEvents) => Promise<number>): Promise<number> {
|
|
327
|
+
const events: StageEvents = {};
|
|
328
|
+
try {
|
|
329
|
+
return await run(events);
|
|
330
|
+
} catch (err) {
|
|
331
|
+
events.stream?.settle("failed", err instanceof Error ? err.message : String(err));
|
|
332
|
+
throw err;
|
|
333
|
+
} finally {
|
|
334
|
+
events.stream?.settle("failed", `${stage} stopped before it finished`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** chant components pr-plan --base <ref> --pr <n> [--env <env>] [--gate <name>] [--forge <kind>] [--output <dir>] [--json] [--events-json] */
|
|
339
|
+
export function runComponentsPrPlan(ctx: CommandContext): Promise<number> {
|
|
340
|
+
if (refusesEventsWithJson(ctx.args, "the stage's report")) return Promise.resolve(1);
|
|
341
|
+
return withStageEvents("pr-plan", (events) => prPlan(ctx, events));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function prPlan(ctx: CommandContext, stage: StageEvents): Promise<number> {
|
|
289
345
|
if (ctx.args.pr === undefined) {
|
|
290
346
|
return fail("pr-plan needs the pull or merge request: --pr <n>", "Its approval is recorded under op pr-<n>, so one pull request's approval never answers another's.");
|
|
291
347
|
}
|
|
292
348
|
const p = await prepare(ctx, "plan");
|
|
293
349
|
if (typeof p === "number") return p;
|
|
294
|
-
const set = await planAll(p);
|
|
295
350
|
const op = prOp(ctx.args.pr, p.member);
|
|
351
|
+
// `--events-json` (#4041): planning, then where the approval stands.
|
|
352
|
+
const events = await openCliRunEvents(ctx.args, { kind: "component", name: `pr-plan ${op}`, cwd: resolve(".") });
|
|
353
|
+
stage.stream = events;
|
|
354
|
+
events?.emit({ type: "run-start", waves: p.plan.waves });
|
|
355
|
+
const set = await planAll(p, events?.emit);
|
|
296
356
|
|
|
297
357
|
let approval: PrApproval = { status: "pending" };
|
|
298
358
|
if (p.plan.order.length > 0) {
|
|
299
359
|
try {
|
|
300
|
-
|
|
360
|
+
const check = await emitGateStep(events?.emit, { op, gate: p.gate }, () =>
|
|
361
|
+
decidePrGate(readOnlyLedger(gitGateLedgerPort()), { op, gate: p.gate, digest: set.doc.digest }),
|
|
362
|
+
);
|
|
363
|
+
approval = approvalOf(check);
|
|
301
364
|
} catch (err) {
|
|
302
365
|
console.error(formatWarning({ message: `could not read the gate ledger, so the approval reads as pending: ${err instanceof Error ? err.message : String(err)}` }));
|
|
303
366
|
}
|
|
@@ -319,6 +382,8 @@ export async function runComponentsPrPlan(ctx: CommandContext): Promise<number>
|
|
|
319
382
|
status,
|
|
320
383
|
});
|
|
321
384
|
await finish(ctx, p, report);
|
|
385
|
+
// The plan stage settles on its planning: an approval still to come is what it reports, not a stop.
|
|
386
|
+
events?.settle(settleOf(status), status === "plan-failed" ? "a member failed to plan" : undefined);
|
|
322
387
|
|
|
323
388
|
console.error(formatInfo(`pr-plan: ${report.members.length} member(s), ${status}`));
|
|
324
389
|
printMembers(report);
|
|
@@ -329,8 +394,13 @@ export async function runComponentsPrPlan(ctx: CommandContext): Promise<number>
|
|
|
329
394
|
return status === "plan-failed" ? 1 : 0;
|
|
330
395
|
}
|
|
331
396
|
|
|
332
|
-
/** chant components pr-apply --base <ref> [--pr <n>] [--env <env>] [--gate <name>] [--forge <kind>] [--require-review] [--output <dir>] [--json] */
|
|
333
|
-
export
|
|
397
|
+
/** chant components pr-apply --base <ref> [--pr <n>] [--env <env>] [--gate <name>] [--forge <kind>] [--require-review] [--output <dir>] [--json] [--events-json] */
|
|
398
|
+
export function runComponentsPrApply(ctx: CommandContext): Promise<number> {
|
|
399
|
+
if (refusesEventsWithJson(ctx.args, "the stage's report")) return Promise.resolve(1);
|
|
400
|
+
return withStageEvents("pr-apply", (events) => prApply(ctx, events));
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function prApply(ctx: CommandContext, stage: StageEvents): Promise<number> {
|
|
334
404
|
const { args } = ctx;
|
|
335
405
|
if (args.requireReview && !args.forge) return fail("--require-review reads the pull request's reviews, so it needs --forge <kind>");
|
|
336
406
|
const p = await prepare(ctx, "apply");
|
|
@@ -345,6 +415,12 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
|
|
|
345
415
|
}
|
|
346
416
|
}
|
|
347
417
|
const op = pr === null ? "pr-unknown" : prOp(pr, p.member);
|
|
418
|
+
// The run id the release records carry, chosen before the run so that
|
|
419
|
+
// `--events-json` (#4041, #4007) names the run by it too.
|
|
420
|
+
const release = autoReleaseRun(`${op}-${Date.now()}`);
|
|
421
|
+
const events = await openCliRunEvents(args, { kind: "component", name: `pr-apply ${op}`, runId: release.runId, cwd: resolve(".") });
|
|
422
|
+
stage.stream = events;
|
|
423
|
+
events?.emit({ type: "run-start", waves: p.plan.waves });
|
|
348
424
|
|
|
349
425
|
// `--resume` (#3464): an earlier attempt at this change on this commit
|
|
350
426
|
// applied part of an approved set. When the members left are covered by
|
|
@@ -360,10 +436,12 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
|
|
|
360
436
|
}
|
|
361
437
|
const resumed = pr !== null && p.plan.order.length > 0 && attempt ? await tryResume(p, attempt, args.resume!, op) : undefined;
|
|
362
438
|
|
|
363
|
-
|
|
439
|
+
if (resumed) emitResumedGate(events, op, p.gate, resumed.check);
|
|
440
|
+
const set = resumed?.set ?? (await planAll(p, events?.emit));
|
|
364
441
|
const base = { stage: "apply" as const, pr, env: p.env, ...(p.member ? { member: p.member } : {}), base: p.base, head: p.head, op, gate: p.gate, plan: p.plan, signal: p.signal, set };
|
|
365
442
|
|
|
366
|
-
const stop = async (report: PrReport, code: number): Promise<number> => {
|
|
443
|
+
const stop = async (report: PrReport, code: number, settle?: RunEventSettleStatus): Promise<number> => {
|
|
444
|
+
events?.settle(settle ?? settleOf(report.status), report.status === "plan-failed" || report.status === "failed" ? report.message ?? `pr-apply ${report.status}` : undefined);
|
|
367
445
|
await finish(ctx, p, report);
|
|
368
446
|
console.error((code === 0 ? formatInfo : (m: string) => formatWarning({ message: m }))(`pr-apply: ${report.status}${report.refusal ? ` (${report.refusal})` : ""}`));
|
|
369
447
|
if (report.message) console.error(report.message);
|
|
@@ -398,16 +476,32 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
|
|
|
398
476
|
);
|
|
399
477
|
}
|
|
400
478
|
|
|
479
|
+
// #4022: keep the change set the gate binds on chant/lifecycle under its
|
|
480
|
+
// digest before deciding the gate, so the plan an approval bound, or the
|
|
481
|
+
// one a pending fact waits on, can be read once this job is gone.
|
|
482
|
+
await storeChangeSetForGate(ctx, p, set.doc);
|
|
483
|
+
|
|
401
484
|
let approval: PrApproval;
|
|
402
485
|
if (resumed) {
|
|
403
486
|
approval = resumed.approval;
|
|
404
487
|
} else {
|
|
405
|
-
const check = await
|
|
406
|
-
|
|
407
|
-
gate: p.gate,
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
488
|
+
const check = await emitGateStep(
|
|
489
|
+
events?.emit,
|
|
490
|
+
{ op, gate: p.gate },
|
|
491
|
+
() =>
|
|
492
|
+
decidePrGate(gitGateLedgerPort(), {
|
|
493
|
+
op,
|
|
494
|
+
gate: p.gate,
|
|
495
|
+
digest: set.doc.digest,
|
|
496
|
+
description: `pull request #${pr}: ${p.plan.order.join(", ")}`,
|
|
497
|
+
}),
|
|
498
|
+
(unapproved) => {
|
|
499
|
+
const a = approvalOf(unapproved);
|
|
500
|
+
return a.status === "changed"
|
|
501
|
+
? describePlanChanged({ op, gate: p.gate, digest: set.doc.digest, approval: a })
|
|
502
|
+
: `No approval stands for this plan. Approve it: ${prApproveCommand({ op, gate: p.gate, digest: set.doc.digest })}`;
|
|
503
|
+
},
|
|
504
|
+
);
|
|
411
505
|
approval = approvalOf(check);
|
|
412
506
|
if (!check.satisfied) {
|
|
413
507
|
const partial = { ...base, approval, status: "refused" as const };
|
|
@@ -431,19 +525,12 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
|
|
|
431
525
|
}
|
|
432
526
|
const strangers = approversWithoutReview(approval.approvedBy ?? [], reviewers);
|
|
433
527
|
if (strangers.length > 0) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
message:
|
|
441
|
-
`The plan is approved by ${strangers.join(", ")}, who ${strangers.length === 1 ? "has" : "have"} no approving review on #${pr}` +
|
|
442
|
-
` (approving reviews: ${reviewers.join(", ") || "none"}). Nothing was applied. ` +
|
|
443
|
-
`An approval counts when it names the reviewer the way the forge does: ${p.forge.principalOf("<login>")}.`,
|
|
444
|
-
}),
|
|
445
|
-
GATED_EXIT_CODE,
|
|
446
|
-
);
|
|
528
|
+
const message =
|
|
529
|
+
`The plan is approved by ${strangers.join(", ")}, who ${strangers.length === 1 ? "has" : "have"} no approving review on #${pr}` +
|
|
530
|
+
` (approving reviews: ${reviewers.join(", ") || "none"}). Nothing was applied. ` +
|
|
531
|
+
`An approval counts when it names the reviewer the way the forge does: ${p.forge.principalOf("<login>")}.`;
|
|
532
|
+
emitReviewStop(events, message);
|
|
533
|
+
return stop(prReport({ ...base, approval, status: "refused", refusal: "not-a-reviewer", message }), GATED_EXIT_CODE);
|
|
447
534
|
}
|
|
448
535
|
}
|
|
449
536
|
|
|
@@ -482,6 +569,8 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
|
|
|
482
569
|
env: p.env,
|
|
483
570
|
set,
|
|
484
571
|
...(resumed ? { resumed: prior } : {}),
|
|
572
|
+
// The apply is this run's waves, so the fan-out's own run framing is dropped.
|
|
573
|
+
...(events ? { onRunEvent: events.nested } : {}),
|
|
485
574
|
...(resumePath
|
|
486
575
|
? {
|
|
487
576
|
onComponentSettled: (settled, outputs) => {
|
|
@@ -497,7 +586,7 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
|
|
|
497
586
|
: {}),
|
|
498
587
|
});
|
|
499
588
|
recordAttempt();
|
|
500
|
-
await recordAutoReleasesForRun(applied.run.results, p.env,
|
|
589
|
+
await recordAutoReleasesForRun(applied.run.results, p.env, release.runId, resolveAutoReleaseDisabled(p.config, args.noReleaseRecord), release.runOrigin);
|
|
501
590
|
const report = prReport({
|
|
502
591
|
...base,
|
|
503
592
|
approval,
|
|
@@ -505,14 +594,59 @@ export async function runComponentsPrApply(ctx: CommandContext): Promise<number>
|
|
|
505
594
|
members: applied.members,
|
|
506
595
|
...(resumed ? { resumed: resumed.completed } : {}),
|
|
507
596
|
});
|
|
508
|
-
|
|
597
|
+
// A component that stopped at a gate of its own leaves the apply waiting on a person.
|
|
598
|
+
const code = await stop(report, applied.status === "applied" ? 0 : 1, applied.run.status === "gated" ? "gated" : undefined);
|
|
509
599
|
if (applied.status === "applied") console.error(formatSuccess(`applied the plan ${report.digest} approved by ${(approval.approvedBy ?? []).join(", ")}`));
|
|
510
600
|
return code;
|
|
511
601
|
}
|
|
512
602
|
|
|
603
|
+
/** A resumed apply's gate (#3464), decided against the approved digest, as the run's `gate` phase. */
|
|
604
|
+
function emitResumedGate(events: RunEventStream | undefined, op: string, gate: string, check: GateCheck): void {
|
|
605
|
+
if (!events) return;
|
|
606
|
+
const where = { component: null, phase: GATE_PHASE, step: `gate:${gate}` };
|
|
607
|
+
events.emit({ type: "phase-start", component: null, phase: GATE_PHASE });
|
|
608
|
+
events.emit({ type: "step-start", ...where });
|
|
609
|
+
for (const body of gateEventsOf(check, { ...where, op, gate }, { durationMs: null })) events.emit(body);
|
|
610
|
+
events.emit({ type: "phase-settle", component: null, phase: GATE_PHASE, status: check.satisfied ? "ok" : "gated" });
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/** An approval by someone with no approving review (#3183) stops the apply: a `review` phase that settles `gated`. */
|
|
614
|
+
function emitReviewStop(events: RunEventStream | undefined, message: string): void {
|
|
615
|
+
if (!events) return;
|
|
616
|
+
events.emit({ type: "phase-start", component: null, phase: "review" });
|
|
617
|
+
events.emit({ type: "step-start", component: null, phase: "review", step: "review" });
|
|
618
|
+
events.emit({ type: "step-settle", component: null, phase: "review", step: "review", status: "gated", durationMs: null, message });
|
|
619
|
+
events.emit({ type: "phase-settle", component: null, phase: "review", status: "gated" });
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Store `doc` under its digest with its entries mapped onto the member's
|
|
624
|
+
* graph (`storeGateChangeSet`), and push. A failure is a warning: the gate
|
|
625
|
+
* decides the same either way.
|
|
626
|
+
*/
|
|
627
|
+
async function storeChangeSetForGate(ctx: CommandContext, p: Prepared, doc: PrPlanSet["doc"]): Promise<void> {
|
|
628
|
+
const { storeGateChangeSet, gateGraphReader } = await import("../../lifecycle/gate-change-set");
|
|
629
|
+
const { pushLifecycleReport } = await import("../../lifecycle/git");
|
|
630
|
+
const graphArgs = [
|
|
631
|
+
...(ctx.args.param ?? []).flatMap((kv) => ["--param", kv]),
|
|
632
|
+
...(ctx.args.paramsFile ? ["--params-file", ctx.args.paramsFile] : []),
|
|
633
|
+
];
|
|
634
|
+
try {
|
|
635
|
+
const stored = await storeGateChangeSet({ doc, cwd: resolve("."), at: p.head, readGraph: gateGraphReader(graphArgs) });
|
|
636
|
+
if (stored.warning) console.error(formatWarning({ message: stored.warning }));
|
|
637
|
+
// The push's own outcome is said by the gate's pending fact, or by the
|
|
638
|
+
// release records the apply writes, which push the same branch.
|
|
639
|
+
if (stored.written) await pushLifecycleReport();
|
|
640
|
+
} catch (err) {
|
|
641
|
+
console.error(formatWarning({ message: `could not store the change set ${doc.digest}: ${err instanceof Error ? err.message : String(err)}` }));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
513
645
|
interface Resumed {
|
|
514
646
|
plan: FanOutPlan;
|
|
515
647
|
set: PrPlanSet;
|
|
648
|
+
/** The gate's decision against the approved digest. */
|
|
649
|
+
check: GateCheck;
|
|
516
650
|
approval: PrApproval;
|
|
517
651
|
completed: string[];
|
|
518
652
|
outputs: Record<string, Record<string, unknown>>;
|
|
@@ -557,5 +691,5 @@ async function tryResume(p: Prepared, attempt: FanOutAttempt, file: string, op:
|
|
|
557
691
|
});
|
|
558
692
|
if (!resumed.ok) return skip(`what is left is not what was approved. ${resumed.reasons.join("; ")}`);
|
|
559
693
|
console.error(formatInfo(`resume : ${attempt.completed.join(", ")} applied in an earlier attempt; applying ${resumed.plan.order.join(", ") || "nothing more"} under the approval of ${record.digest}`));
|
|
560
|
-
return { plan: resumed.plan, set: resumed.set, approval: approvalOf(check), completed: attempt.completed, outputs };
|
|
694
|
+
return { plan: resumed.plan, set: resumed.set, check, approval: approvalOf(check), completed: attempt.completed, outputs };
|
|
561
695
|
}
|