@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,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The emitters' side of the run-event contract (#4041): what a runtime or a
|
|
3
|
+
* command needs to print a run's stream through the one writer in
|
|
4
|
+
* `./run-event.ts`.
|
|
5
|
+
*
|
|
6
|
+
* Every emitter hands bodies to a {@link RunEventSink}. The CLI opens a
|
|
7
|
+
* {@link RunEventStream} over a run's identity, which stamps each body
|
|
8
|
+
* through `createRunEventWriter` and makes sure the stream opens with
|
|
9
|
+
* `run-start` and ends with one `run-settle`, even when the run threw before
|
|
10
|
+
* it said so itself. The components driver emits bodies only, and
|
|
11
|
+
* {@link progressOfRunEvent} turns them back into the older
|
|
12
|
+
* `RunProgressEvent` lines that `--progress-json` still prints.
|
|
13
|
+
*
|
|
14
|
+
* Kept apart from `./run-event.ts`, which a level-0 import loads: this
|
|
15
|
+
* module reads git and the CI environment.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { RunProgressEvent } from "./components/run-progress";
|
|
19
|
+
import type { GateCheck } from "./op/gate";
|
|
20
|
+
import { resolveCommitSha } from "./lifecycle/commit-sha";
|
|
21
|
+
import { resolveCiRun, type RunOrigin } from "./lifecycle/release-ledger";
|
|
22
|
+
import {
|
|
23
|
+
createRunEventWriter,
|
|
24
|
+
type RunEventBody,
|
|
25
|
+
type RunEventKind,
|
|
26
|
+
type RunEventRun,
|
|
27
|
+
type RunEventRuntime,
|
|
28
|
+
type RunEventSettleStatus,
|
|
29
|
+
} from "./run-event";
|
|
30
|
+
|
|
31
|
+
/** Where an emitter hands a run's events, before the writer stamps them. */
|
|
32
|
+
export type RunEventSink = (body: RunEventBody) => void;
|
|
33
|
+
|
|
34
|
+
/** One sink that hands each body to every sink given, or undefined when none is. */
|
|
35
|
+
export function joinRunEventSinks(...sinks: Array<RunEventSink | undefined>): RunEventSink | undefined {
|
|
36
|
+
const live = sinks.filter((s): s is RunEventSink => s !== undefined);
|
|
37
|
+
if (live.length === 0) return undefined;
|
|
38
|
+
if (live.length === 1) return live[0];
|
|
39
|
+
return (body) => {
|
|
40
|
+
for (const sink of live) sink(body);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const GATE_PREFIX = "gate:";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A run-event body as the older `RunProgressEvent`, or undefined when that
|
|
48
|
+
* stream had no such event. It never printed a gate step, a skipped step,
|
|
49
|
+
* a phase outside a component or a gate's decision, so those map to nothing
|
|
50
|
+
* and `--progress-json` prints what it printed before #4041.
|
|
51
|
+
*/
|
|
52
|
+
export function progressOfRunEvent(body: RunEventBody): RunProgressEvent | undefined {
|
|
53
|
+
const progressStatus = (s: RunEventSettleStatus): "ok" | "failed" | "gated" => (s === "waiting" ? "gated" : s);
|
|
54
|
+
switch (body.type) {
|
|
55
|
+
case "run-start":
|
|
56
|
+
return { type: "run-start", waves: body.waves ?? [] };
|
|
57
|
+
case "wave-start":
|
|
58
|
+
return { type: "wave-start", wave: body.wave, components: body.components };
|
|
59
|
+
case "component-start":
|
|
60
|
+
return { type: "component-start", wave: body.wave, component: body.component };
|
|
61
|
+
case "phase-start":
|
|
62
|
+
return body.component === null ? undefined : { type: "phase-start", component: body.component, phase: body.phase };
|
|
63
|
+
case "step-start":
|
|
64
|
+
if (body.component === null || body.step.startsWith(GATE_PREFIX)) return undefined;
|
|
65
|
+
return { type: "step", component: body.component, phase: body.phase, step: body.step, status: "running" };
|
|
66
|
+
case "step-settle":
|
|
67
|
+
if (body.component === null || body.step.startsWith(GATE_PREFIX)) return undefined;
|
|
68
|
+
if (body.status !== "ok" && body.status !== "failed") return undefined;
|
|
69
|
+
return {
|
|
70
|
+
type: "step",
|
|
71
|
+
component: body.component,
|
|
72
|
+
phase: body.phase,
|
|
73
|
+
step: body.step,
|
|
74
|
+
status: body.status,
|
|
75
|
+
...(body.error !== undefined ? { error: body.error } : {}),
|
|
76
|
+
};
|
|
77
|
+
case "phase-settle":
|
|
78
|
+
return body.component === null
|
|
79
|
+
? undefined
|
|
80
|
+
: { type: "phase-done", component: body.component, phase: body.phase, status: progressStatus(body.status) };
|
|
81
|
+
case "component-settle":
|
|
82
|
+
return { type: "component-done", wave: body.wave, component: body.component, status: progressStatus(body.status) };
|
|
83
|
+
case "wave-settle":
|
|
84
|
+
return { type: "wave-done", wave: body.wave, status: progressStatus(body.status) };
|
|
85
|
+
case "run-settle":
|
|
86
|
+
return { type: "run-done", status: progressStatus(body.status) };
|
|
87
|
+
case "gate-waiting":
|
|
88
|
+
case "gate-resolved":
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A `RunProgressEvent` callback as a sink of run-event bodies, through {@link progressOfRunEvent}. */
|
|
94
|
+
export function progressSinkOf(onProgress: ((event: RunProgressEvent) => void) | undefined): RunEventSink | undefined {
|
|
95
|
+
if (!onProgress) return undefined;
|
|
96
|
+
return (body) => {
|
|
97
|
+
const event = progressOfRunEvent(body);
|
|
98
|
+
if (event) onProgress(event);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The CI run the environment names, as `resolveCiRun` gives it. */
|
|
103
|
+
type CiRun = { runId: string; runOrigin: RunOrigin };
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A run's identity from what the emitter already knows. The runtime is `ci`
|
|
107
|
+
* inside a CI job, `steward` in a steward's turn and `local` otherwise; the
|
|
108
|
+
* origin is the CI run's, `op` for a steward's run, and `local` otherwise. An
|
|
109
|
+
* Op run keeps its own run id in CI, as its run record does (#4020).
|
|
110
|
+
*/
|
|
111
|
+
export function runEventRunOf(input: {
|
|
112
|
+
kind: RunEventKind;
|
|
113
|
+
name: string;
|
|
114
|
+
runId: string;
|
|
115
|
+
gitSha?: string;
|
|
116
|
+
ci?: CiRun;
|
|
117
|
+
steward?: boolean;
|
|
118
|
+
runtime?: RunEventRuntime;
|
|
119
|
+
runOrigin?: RunOrigin;
|
|
120
|
+
}): RunEventRun {
|
|
121
|
+
return {
|
|
122
|
+
runId: input.runId,
|
|
123
|
+
kind: input.kind,
|
|
124
|
+
name: input.name,
|
|
125
|
+
gitSha: input.gitSha ?? null,
|
|
126
|
+
runtime: input.runtime ?? (input.ci ? "ci" : input.steward ? "steward" : "local"),
|
|
127
|
+
runOrigin: input.runOrigin ?? input.ci?.runOrigin ?? (input.steward ? { forge: "op" } : { forge: "local" }),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A run's identity, resolved: the commit from `resolveCommitSha` and the CI
|
|
133
|
+
* run from `resolveCiRun` (#4020). Without `runId`, a CI run is named by its
|
|
134
|
+
* CI run id and any other by a locally minted `local-<ms>` id, the spelling
|
|
135
|
+
* `resolveRunId` uses for a release record.
|
|
136
|
+
*/
|
|
137
|
+
export async function resolveRunEventRun(input: {
|
|
138
|
+
kind: RunEventKind;
|
|
139
|
+
name: string;
|
|
140
|
+
runId?: string;
|
|
141
|
+
cwd?: string;
|
|
142
|
+
env?: NodeJS.ProcessEnv;
|
|
143
|
+
}): Promise<RunEventRun> {
|
|
144
|
+
const env = input.env ?? process.env;
|
|
145
|
+
const ci = resolveCiRun(env);
|
|
146
|
+
const gitSha = await resolveCommitSha({ ...(input.cwd ? { cwd: input.cwd } : {}), env });
|
|
147
|
+
const runId = input.runId ?? ci?.runId ?? `local-${Date.now()}`;
|
|
148
|
+
// An explicit id that is not the CI run's is one the caller minted, so the
|
|
149
|
+
// origin says local, as `resolveRunId` records it on a release.
|
|
150
|
+
const inCiSpace = ci !== undefined && runId === ci.runId;
|
|
151
|
+
return runEventRunOf({
|
|
152
|
+
kind: input.kind,
|
|
153
|
+
name: input.name,
|
|
154
|
+
runId,
|
|
155
|
+
...(gitSha ? { gitSha } : {}),
|
|
156
|
+
...(ci ? { ci } : {}),
|
|
157
|
+
...(ci && !inCiSpace ? { runOrigin: { forge: "local" } } : {}),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** One run's stream: the writer, held to the shape the contract promises. */
|
|
162
|
+
export interface RunEventStream {
|
|
163
|
+
/** The run every event names. */
|
|
164
|
+
readonly run: RunEventRun;
|
|
165
|
+
/** Write a body. */
|
|
166
|
+
emit: RunEventSink;
|
|
167
|
+
/**
|
|
168
|
+
* A sink for a part of the run that frames itself as a whole run, such as
|
|
169
|
+
* the fan-out a pull request's apply runs: its `run-start` and
|
|
170
|
+
* `run-settle` are dropped, and the caller settles the run.
|
|
171
|
+
*/
|
|
172
|
+
nested: RunEventSink;
|
|
173
|
+
/** End the stream with `run-settle`, unless it has ended. */
|
|
174
|
+
settle(status: RunEventSettleStatus, error?: string): void;
|
|
175
|
+
/** Whether `run-settle` has been written. */
|
|
176
|
+
readonly settled: boolean;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Open a run's stream over `write`. The first event is always `run-start`
|
|
181
|
+
* (one with `waves: null` is written first when the emitter starts with
|
|
182
|
+
* something else), a second `run-start` is dropped, `run-settle` gets the
|
|
183
|
+
* time since `run-start` when the emitter did not time it, and nothing is
|
|
184
|
+
* written after `run-settle`.
|
|
185
|
+
*/
|
|
186
|
+
export function openRunEventStream(
|
|
187
|
+
run: RunEventRun,
|
|
188
|
+
write: (line: string) => void,
|
|
189
|
+
opts: { now?: () => Date; chant?: string } = {},
|
|
190
|
+
): RunEventStream {
|
|
191
|
+
const now = opts.now ?? (() => new Date());
|
|
192
|
+
const writer = createRunEventWriter({ run, write, now, ...(opts.chant ? { chant: opts.chant } : {}) });
|
|
193
|
+
let startedAt: number | undefined;
|
|
194
|
+
let settled = false;
|
|
195
|
+
|
|
196
|
+
const emit: RunEventSink = (body) => {
|
|
197
|
+
if (settled) return;
|
|
198
|
+
if (body.type === "run-start") {
|
|
199
|
+
if (startedAt !== undefined) return;
|
|
200
|
+
startedAt = now().getTime();
|
|
201
|
+
writer.emit(body);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (startedAt === undefined) emit({ type: "run-start", waves: null });
|
|
205
|
+
if (body.type === "run-settle") {
|
|
206
|
+
settled = true;
|
|
207
|
+
writer.emit(body.durationMs === null ? { ...body, durationMs: Math.max(0, now().getTime() - startedAt!) } : body);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
writer.emit(body);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
run,
|
|
215
|
+
emit,
|
|
216
|
+
nested: (body) => {
|
|
217
|
+
if (body.type !== "run-start" && body.type !== "run-settle") emit(body);
|
|
218
|
+
},
|
|
219
|
+
settle(status, error) {
|
|
220
|
+
if (settled) return;
|
|
221
|
+
emit({ type: "run-settle", status, durationMs: null, ...(error !== undefined ? { error } : {}) });
|
|
222
|
+
},
|
|
223
|
+
get settled() {
|
|
224
|
+
return settled;
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Where a gate step sits, for {@link gateEventsOf}. */
|
|
230
|
+
export interface GateStepAt {
|
|
231
|
+
component: string | null;
|
|
232
|
+
phase: string;
|
|
233
|
+
/** `gate:<name>`. */
|
|
234
|
+
step: string;
|
|
235
|
+
index?: number;
|
|
236
|
+
/** The op the gate is recorded under. */
|
|
237
|
+
op: string;
|
|
238
|
+
gate: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* A decided gate as events: `gate-resolved` and an `ok` settle when an
|
|
243
|
+
* approval stands, `gate-waiting` and a `gated` settle when none does.
|
|
244
|
+
* `message` says why it did not pass, for a person.
|
|
245
|
+
*/
|
|
246
|
+
export function gateEventsOf(check: GateCheck, at: GateStepAt, opts: { durationMs: number | null; message?: string }): RunEventBody[] {
|
|
247
|
+
const where = { component: at.component, phase: at.phase, step: at.step, ...(at.index !== undefined ? { index: at.index } : {}) };
|
|
248
|
+
const message = opts.message !== undefined ? { message: opts.message } : {};
|
|
249
|
+
if (check.satisfied) {
|
|
250
|
+
return [
|
|
251
|
+
{
|
|
252
|
+
type: "gate-resolved",
|
|
253
|
+
...where,
|
|
254
|
+
op: at.op,
|
|
255
|
+
gate: at.gate,
|
|
256
|
+
resolvedBy: check.resolution.resolvedBy,
|
|
257
|
+
resolvedAt: check.resolution.timestamp,
|
|
258
|
+
via: check.via ?? null,
|
|
259
|
+
...(check.resolution.url ? { url: check.resolution.url } : {}),
|
|
260
|
+
},
|
|
261
|
+
{ type: "step-settle", ...where, status: "ok", durationMs: opts.durationMs },
|
|
262
|
+
];
|
|
263
|
+
}
|
|
264
|
+
return [
|
|
265
|
+
{ type: "gate-waiting", ...where, op: check.pending.op, gate: at.gate, point: null, ...message },
|
|
266
|
+
{ type: "step-settle", ...where, status: "gated", durationMs: opts.durationMs, ...message },
|
|
267
|
+
];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The phase a gate outside any component is the one step of: a fan-out's, a wave's or a pull request's. */
|
|
271
|
+
export const GATE_PHASE = "gate";
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Decide a gate that stands outside any component, and emit it as the one
|
|
275
|
+
* step of a {@link GATE_PHASE} phase: `phase-start`, `step-start`, the
|
|
276
|
+
* decision through {@link gateEventsOf}, the settle and `phase-settle`. A
|
|
277
|
+
* decision that throws settles the step `failed` and is rethrown. `message`
|
|
278
|
+
* says, for a person, why a gate that did not pass did not.
|
|
279
|
+
*/
|
|
280
|
+
export async function emitGateStep(
|
|
281
|
+
emit: RunEventSink | undefined,
|
|
282
|
+
at: { op: string; gate: string },
|
|
283
|
+
decide: () => Promise<GateCheck>,
|
|
284
|
+
message?: (check: Extract<GateCheck, { satisfied: false }>) => string | undefined,
|
|
285
|
+
): Promise<GateCheck> {
|
|
286
|
+
const where = { component: null, phase: GATE_PHASE, step: `gate:${at.gate}` };
|
|
287
|
+
emit?.({ type: "phase-start", component: null, phase: GATE_PHASE });
|
|
288
|
+
emit?.({ type: "step-start", ...where });
|
|
289
|
+
const start = Date.now();
|
|
290
|
+
let check: GateCheck;
|
|
291
|
+
try {
|
|
292
|
+
check = await decide();
|
|
293
|
+
} catch (err) {
|
|
294
|
+
emit?.({ type: "step-settle", ...where, status: "failed", durationMs: Date.now() - start, error: err instanceof Error ? err.message : String(err) });
|
|
295
|
+
emit?.({ type: "phase-settle", component: null, phase: GATE_PHASE, status: "failed" });
|
|
296
|
+
throw err;
|
|
297
|
+
}
|
|
298
|
+
const why = check.satisfied
|
|
299
|
+
? undefined
|
|
300
|
+
: (message?.(check) ?? (check.mismatch ? "an approval stands for this gate, but for another plan" : undefined));
|
|
301
|
+
for (const body of gateEventsOf(check, { ...where, op: at.op, gate: at.gate }, { durationMs: Date.now() - start, ...(why !== undefined ? { message: why } : {}) })) {
|
|
302
|
+
emit?.(body);
|
|
303
|
+
}
|
|
304
|
+
emit?.({ type: "phase-settle", component: null, phase: GATE_PHASE, status: check.satisfied ? "ok" : "gated" });
|
|
305
|
+
return check;
|
|
306
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4019: the run-event contract. One schema for Op, component and pipeline
|
|
3
|
+
* runs, the one writer, and the two older streams mapped onto it. Every
|
|
4
|
+
* line the writer prints is checked against `run-event.schema.json`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "vitest";
|
|
8
|
+
import type { RunProgressEvent } from "./components/run-progress";
|
|
9
|
+
import type { StepRecord } from "./op/local-executor";
|
|
10
|
+
import { contract, validSchema } from "./workspace/__fixtures__/contract-repo";
|
|
11
|
+
import { READ_CONTRACT_FLOOR, READ_CONTRACT_VERSION } from "./workspace/reason-codes";
|
|
12
|
+
import {
|
|
13
|
+
buildRunEvent,
|
|
14
|
+
createRunEventWriter,
|
|
15
|
+
RUN_EVENT_CONTRACT_VERSION,
|
|
16
|
+
RUN_EVENT_FLOOR,
|
|
17
|
+
RUN_EVENT_SCHEMA_ID,
|
|
18
|
+
RUN_EVENT_TYPES,
|
|
19
|
+
runEventOfProgress,
|
|
20
|
+
runEventsOfStepRecord,
|
|
21
|
+
serializeRunEvent,
|
|
22
|
+
type RunEvent,
|
|
23
|
+
type RunEventBody,
|
|
24
|
+
type RunEventRun,
|
|
25
|
+
} from "./run-event";
|
|
26
|
+
import schema from "./workspace/run-event.schema.json";
|
|
27
|
+
|
|
28
|
+
const { validate, expectValid } = contract(schema);
|
|
29
|
+
|
|
30
|
+
const SHA = "0123456789abcdef0123456789abcdef01234567";
|
|
31
|
+
|
|
32
|
+
const OP_RUN: RunEventRun = {
|
|
33
|
+
runId: "6f1c2d4e-0000-4000-8000-000000000001",
|
|
34
|
+
kind: "op",
|
|
35
|
+
name: "deploy-api",
|
|
36
|
+
gitSha: SHA,
|
|
37
|
+
runtime: "local",
|
|
38
|
+
runOrigin: { forge: "local" },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** A writer that keeps its lines, on a clock that ticks a second per event. */
|
|
42
|
+
function capture(run: RunEventRun) {
|
|
43
|
+
const lines: string[] = [];
|
|
44
|
+
let t = Date.parse("2026-10-16T12:00:00.000Z");
|
|
45
|
+
const writer = createRunEventWriter({ run, write: (l) => lines.push(l), now: () => new Date((t += 1000)), chant: "0.124.28" });
|
|
46
|
+
const events = () => lines.map((l) => JSON.parse(l) as RunEvent);
|
|
47
|
+
return { writer, lines, events };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("the run-event schema", () => {
|
|
51
|
+
test("is a valid draft 2020-12 document at the contract version, naming its floor and the read contract's", () => {
|
|
52
|
+
expect(validSchema(schema)).toBe(true);
|
|
53
|
+
expect(schema.$id).toBe(RUN_EVENT_SCHEMA_ID);
|
|
54
|
+
expect(RUN_EVENT_SCHEMA_ID).toBe(`https://intentius.io/chant/schemas/workspace/run-event/v${READ_CONTRACT_VERSION}/run-event.schema.json`);
|
|
55
|
+
const envelope = schema.$defs.envelope.properties;
|
|
56
|
+
expect(envelope.contract).toEqual({ const: RUN_EVENT_CONTRACT_VERSION });
|
|
57
|
+
expect(envelope.$schema).toEqual({ const: RUN_EVENT_SCHEMA_ID });
|
|
58
|
+
expect(schema.description).toContain(`written by chant ${RUN_EVENT_FLOOR} and newer`);
|
|
59
|
+
expect(schema.description).toContain(`chant ${READ_CONTRACT_FLOOR} and newer`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("names exactly the event types the code does, and every one requires the envelope", () => {
|
|
63
|
+
const defs = schema.$defs as Record<string, { allOf?: Array<{ $ref?: string; properties?: { type?: { const?: string } } }> }>;
|
|
64
|
+
const refs = schema.oneOf.map((o) => o.$ref.replace("#/$defs/", ""));
|
|
65
|
+
expect([...refs].sort()).toEqual([...RUN_EVENT_TYPES].sort());
|
|
66
|
+
for (const t of RUN_EVENT_TYPES) {
|
|
67
|
+
const parts = defs[t].allOf!;
|
|
68
|
+
expect(parts[0].$ref, t).toBe("#/$defs/envelope");
|
|
69
|
+
expect(parts[1].properties!.type!.const, t).toBe(t);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("keeps the step statuses closed, with skipped and room for cached", () => {
|
|
74
|
+
const status = schema.$defs["step-settle"].allOf[1].properties!.status as { enum: string[] };
|
|
75
|
+
expect(status.enum).toEqual(["ok", "failed", "skipped", "cached", "gated", "waiting"]);
|
|
76
|
+
const one = buildRunEvent({ type: "step-settle", component: null, phase: "p", step: "s", status: "ok", durationMs: 1 }, { run: OP_RUN, seq: 3 });
|
|
77
|
+
expectValid(one);
|
|
78
|
+
for (const bad of ["fail", "running", "done"]) expect(validate({ ...one, status: bad }), bad).toBe(false);
|
|
79
|
+
expect(validate({ ...one, status: "skipped", skip: "receipt" })).toBe(true);
|
|
80
|
+
expect(validate({ ...one, status: "skipped", skip: "because" })).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("refuses an event missing the run, the commit or the runtime, or at another contract", () => {
|
|
84
|
+
const one = buildRunEvent({ type: "run-start", waves: null }, { run: OP_RUN, seq: 0 });
|
|
85
|
+
expectValid(one);
|
|
86
|
+
for (const field of ["runId", "kind", "name", "gitSha", "runtime", "runOrigin", "seq", "at", "chant"] as const) {
|
|
87
|
+
const copy: Record<string, unknown> = { ...one };
|
|
88
|
+
delete copy[field];
|
|
89
|
+
expect(validate(copy), field).toBe(false);
|
|
90
|
+
}
|
|
91
|
+
expect(validate({ ...one, gitSha: "main" })).toBe(false);
|
|
92
|
+
expect(validate({ ...one, gitSha: null })).toBe(true);
|
|
93
|
+
expect(validate({ ...one, runtime: "cloud" })).toBe(false);
|
|
94
|
+
expect(validate({ ...one, runOrigin: { forge: "jenkins" } })).toBe(false);
|
|
95
|
+
expect(validate({ ...one, contract: 2 })).toBe(false);
|
|
96
|
+
expect(validate({ ...one, type: "run-done" })).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("lets a reader ignore fields it does not know", () => {
|
|
100
|
+
expectValid({ ...buildRunEvent({ type: "run-start", waves: null }, { run: OP_RUN, seq: 0 }), later: true });
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("the one writer", () => {
|
|
105
|
+
test("writes an Op run as one line per event, each valid, with the run on every line and seq counting from 0", () => {
|
|
106
|
+
const { writer, lines, events } = capture(OP_RUN);
|
|
107
|
+
const bodies: RunEventBody[] = [
|
|
108
|
+
{ type: "run-start", waves: null },
|
|
109
|
+
{ type: "phase-start", component: null, phase: "build" },
|
|
110
|
+
{ type: "step-start", component: null, phase: "build", step: "shell" },
|
|
111
|
+
{ type: "step-settle", component: null, phase: "build", step: "shell", status: "ok", durationMs: 412 },
|
|
112
|
+
{ type: "step-start", component: null, phase: "build", step: "effect:schema" },
|
|
113
|
+
{ type: "step-settle", component: null, phase: "build", step: "effect:schema", status: "skipped", skip: "receipt", durationMs: 3 },
|
|
114
|
+
{ type: "step-settle", component: null, phase: "build", step: "cache-restore", status: "cached", durationMs: 0 },
|
|
115
|
+
{ type: "phase-settle", component: null, phase: "build", status: "ok" },
|
|
116
|
+
{ type: "phase-start", component: null, phase: "release" },
|
|
117
|
+
{ type: "step-start", component: null, phase: "release", step: "gate:prod" },
|
|
118
|
+
{ type: "gate-waiting", component: null, phase: "release", step: "gate:prod", op: "deploy-api", gate: "prod", point: null },
|
|
119
|
+
{ type: "step-settle", component: null, phase: "release", step: "gate:prod", status: "gated", durationMs: 20 },
|
|
120
|
+
{ type: "phase-settle", component: null, phase: "release", status: "gated" },
|
|
121
|
+
{ type: "run-settle", status: "gated", durationMs: 1500 },
|
|
122
|
+
];
|
|
123
|
+
for (const b of bodies) writer.emit(b);
|
|
124
|
+
expect(writer.next).toBe(bodies.length);
|
|
125
|
+
expect(lines.every((l) => l.endsWith("\n") && !l.slice(0, -1).includes("\n"))).toBe(true);
|
|
126
|
+
const evs = events();
|
|
127
|
+
evs.forEach((e, i) => {
|
|
128
|
+
expectValid(e);
|
|
129
|
+
expect(e.seq).toBe(i);
|
|
130
|
+
expect(e).toMatchObject({ $schema: RUN_EVENT_SCHEMA_ID, contract: 1, chant: "0.124.28", runId: OP_RUN.runId, kind: "op", name: "deploy-api", gitSha: SHA, runtime: "local", runOrigin: { forge: "local" } });
|
|
131
|
+
expect(e.type).toBe(bodies[i].type);
|
|
132
|
+
});
|
|
133
|
+
expect(evs[0].at).toBe("2026-10-16T12:00:01.000Z");
|
|
134
|
+
expect(Object.keys(evs[0]).slice(0, 5)).toEqual(["$schema", "contract", "chant", "seq", "at"]);
|
|
135
|
+
expect(evs.at(-1)).toMatchObject({ type: "run-settle", status: "gated" });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("writes a pipeline run in forge CI, its stages as phases and its jobs as steps, with the CI locator", () => {
|
|
139
|
+
const { writer, events } = capture({
|
|
140
|
+
runId: "8812345",
|
|
141
|
+
kind: "pipeline",
|
|
142
|
+
name: "main",
|
|
143
|
+
gitSha: SHA,
|
|
144
|
+
runtime: "ci",
|
|
145
|
+
runOrigin: { forge: "gitlab", repo: "acme/api", url: "https://gitlab.example.com/acme/api/-/pipelines/8812345" },
|
|
146
|
+
});
|
|
147
|
+
writer.emit({ type: "run-start", waves: null });
|
|
148
|
+
writer.emit({ type: "phase-start", component: null, phase: "test" });
|
|
149
|
+
writer.emit({ type: "step-start", component: null, phase: "test", step: "unit" });
|
|
150
|
+
writer.emit({ type: "step-settle", component: null, phase: "test", step: "unit", status: "failed", durationMs: 9000, error: "exit 1" });
|
|
151
|
+
writer.emit({ type: "phase-settle", component: null, phase: "test", status: "failed" });
|
|
152
|
+
writer.emit({ type: "run-settle", status: "failed", durationMs: 9100, error: "job unit failed" });
|
|
153
|
+
for (const e of events()) expectValid(e);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("writes a steward run with its approval and an open decision point", () => {
|
|
157
|
+
const { writer, events } = capture({ ...OP_RUN, runtime: "steward", runOrigin: { forge: "op", url: "https://fountain.example.com/threads/t-1" } });
|
|
158
|
+
writer.emit({ type: "gate-resolved", component: null, phase: "release", step: "gate:prod", op: "deploy-api", gate: "prod", resolvedBy: "alice", resolvedAt: "2026-10-16T11:00:00.000Z", via: "quorum" });
|
|
159
|
+
writer.emit({ type: "gate-waiting", component: null, phase: "release", step: "pick-region", op: "deploy-api", gate: null, point: "A-0042" });
|
|
160
|
+
writer.emit({ type: "step-settle", component: null, phase: "release", step: "pick-region", status: "waiting", durationMs: 5, message: "region?" });
|
|
161
|
+
writer.emit({ type: "run-settle", status: "waiting", durationMs: 40 });
|
|
162
|
+
for (const e of events()) expectValid(e);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("serializes one event as one JSON line", () => {
|
|
166
|
+
const e = buildRunEvent({ type: "run-start", waves: [["a"], ["b", "c"]] }, { run: OP_RUN, seq: 0, at: "2026-10-16T12:00:00.000Z", chant: "1.0.0" });
|
|
167
|
+
expect(serializeRunEvent(e)).toBe(JSON.stringify(e) + "\n");
|
|
168
|
+
expect(e.at).toBe("2026-10-16T12:00:00.000Z");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("is exported from @intentius/chant", async () => {
|
|
172
|
+
const root = await import("./index");
|
|
173
|
+
expect(root.createRunEventWriter).toBe(createRunEventWriter);
|
|
174
|
+
expect(root.RUN_EVENT_SCHEMA_ID).toBe(RUN_EVENT_SCHEMA_ID);
|
|
175
|
+
}, 60_000);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("RunProgressEvent in terms of the run-event contract", () => {
|
|
179
|
+
const progress: RunProgressEvent[] = [
|
|
180
|
+
{ type: "run-start", waves: [["db"], ["api", "web"]] },
|
|
181
|
+
{ type: "wave-start", wave: 1, components: ["db"] },
|
|
182
|
+
{ type: "component-start", wave: 1, component: "db" },
|
|
183
|
+
{ type: "phase-start", component: "db", phase: "deploy" },
|
|
184
|
+
{ type: "step", component: "db", phase: "deploy", step: "apply", status: "running" },
|
|
185
|
+
{ type: "step", component: "db", phase: "deploy", step: "apply", status: "failed", error: "boom" },
|
|
186
|
+
{ type: "step", component: "db", phase: "deploy", step: "verify", status: "ok" },
|
|
187
|
+
{ type: "phase-done", component: "db", phase: "deploy", status: "failed" },
|
|
188
|
+
{ type: "component-done", wave: 1, component: "db", status: "gated" },
|
|
189
|
+
{ type: "wave-done", wave: 1, status: "failed" },
|
|
190
|
+
{ type: "run-done", status: "failed" },
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
test("maps every variant to a valid event that keeps its fields", () => {
|
|
194
|
+
const { writer, events } = capture({ ...OP_RUN, kind: "component", name: "db,api,web" });
|
|
195
|
+
for (const p of progress) writer.emit(runEventOfProgress(p));
|
|
196
|
+
const evs = events();
|
|
197
|
+
for (const e of evs) expectValid(e);
|
|
198
|
+
expect(evs.map((e) => e.type)).toEqual([
|
|
199
|
+
"run-start",
|
|
200
|
+
"wave-start",
|
|
201
|
+
"component-start",
|
|
202
|
+
"phase-start",
|
|
203
|
+
"step-start",
|
|
204
|
+
"step-settle",
|
|
205
|
+
"step-settle",
|
|
206
|
+
"phase-settle",
|
|
207
|
+
"component-settle",
|
|
208
|
+
"wave-settle",
|
|
209
|
+
"run-settle",
|
|
210
|
+
]);
|
|
211
|
+
expect(evs[0]).toMatchObject({ waves: [["db"], ["api", "web"]] });
|
|
212
|
+
expect(evs[5]).toMatchObject({ component: "db", step: "apply", status: "failed", error: "boom", durationMs: null });
|
|
213
|
+
expect(evs[8]).toMatchObject({ component: "db", status: "gated" });
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("StepRecord in terms of the run-event contract", () => {
|
|
218
|
+
const ctx = { op: "deploy-api" };
|
|
219
|
+
const base = { phase: "release", args: {}, durationMs: 12 } as const;
|
|
220
|
+
const types = (r: StepRecord) => runEventsOfStepRecord(r, ctx).map((b) => b.type);
|
|
221
|
+
const settle = (r: StepRecord) => runEventsOfStepRecord(r, ctx).at(-1)!;
|
|
222
|
+
|
|
223
|
+
test("ok and fail settle ok and failed, with the error", () => {
|
|
224
|
+
expect(settle({ ...base, fn: "shell", status: "ok" })).toEqual({ type: "step-settle", component: null, phase: "release", step: "shell", status: "ok", durationMs: 12 });
|
|
225
|
+
expect(settle({ ...base, fn: "shell", status: "fail", error: "exit 2" })).toMatchObject({ status: "failed", error: "exit 2" });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("an approved gate resolves before its step settles ok", () => {
|
|
229
|
+
const r: StepRecord = { ...base, fn: "gate:prod", status: "ok", approval: { gate: "prod", resolvedBy: "alice", timestamp: "2026-10-16T11:00:00.000Z", via: "policy", url: "https://x" } };
|
|
230
|
+
expect(types(r)).toEqual(["gate-resolved", "step-settle"]);
|
|
231
|
+
expect(runEventsOfStepRecord(r, ctx)[0]).toEqual({ type: "gate-resolved", component: null, phase: "release", step: "gate:prod", op: "deploy-api", gate: "prod", resolvedBy: "alice", resolvedAt: "2026-10-16T11:00:00.000Z", via: "policy", url: "https://x" });
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("a step waiting on a gate or a decision point settles gated or waiting, after gate-waiting", () => {
|
|
235
|
+
const own: StepRecord = { ...base, fn: "workspace-upgrade", status: "skipped", gate: { op: "workspace-upgrade", gate: "all" } };
|
|
236
|
+
expect(types(own)).toEqual(["gate-waiting", "step-settle"]);
|
|
237
|
+
expect(runEventsOfStepRecord(own, ctx)[0]).toMatchObject({ op: "workspace-upgrade", gate: "all", point: null });
|
|
238
|
+
expect(settle(own)).toMatchObject({ status: "gated" });
|
|
239
|
+
|
|
240
|
+
const point: StepRecord = { ...base, fn: "pick", status: "skipped", point: { id: "A-1", point: "region", state: "escalated", path: "answers/A-1.md", subject: null, steward: null } };
|
|
241
|
+
expect(runEventsOfStepRecord(point, ctx)[0]).toMatchObject({ type: "gate-waiting", gate: null, point: "A-1" });
|
|
242
|
+
expect(settle(point)).toMatchObject({ status: "waiting" });
|
|
243
|
+
|
|
244
|
+
const pointGate: StepRecord = { ...point, fn: "gate:prod" };
|
|
245
|
+
expect(runEventsOfStepRecord(pointGate, ctx)[0]).toMatchObject({ gate: "prod", point: "A-1" });
|
|
246
|
+
|
|
247
|
+
const mismatch: StepRecord = { ...base, fn: "gate:prod", status: "skipped", refusal: "approved for another plan" };
|
|
248
|
+
expect(runEventsOfStepRecord(mismatch, ctx)[0]).toMatchObject({ type: "gate-waiting", gate: "prod", message: "approved for another plan" });
|
|
249
|
+
expect(settle(mismatch)).toMatchObject({ status: "gated", message: "approved for another plan" });
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("skips say why when the record does, and null when it does not", () => {
|
|
253
|
+
expect(settle({ ...base, fn: "build", status: "skipped", resumed: { run: "r-1", gate: "prod" } })).toMatchObject({ status: "skipped", skip: "resumed" });
|
|
254
|
+
expect(settle({ ...base, fn: "workLease:claim", status: "skipped", refusal: "no item ready" })).toMatchObject({ status: "skipped", skip: "refused", message: "no item ready" });
|
|
255
|
+
expect(settle({ ...base, fn: "gate:prod", status: "skipped" })).toMatchObject({ status: "skipped", skip: null });
|
|
256
|
+
expect(settle({ ...base, fn: "migrate", status: "skipped", durationMs: 0 })).toMatchObject({ status: "skipped", skip: null, durationMs: 0 });
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("every mapped record writes valid events", () => {
|
|
260
|
+
const { writer, events } = capture(OP_RUN);
|
|
261
|
+
const records: StepRecord[] = [
|
|
262
|
+
{ ...base, fn: "shell", status: "ok" },
|
|
263
|
+
{ ...base, fn: "shell", status: "fail", error: "x" },
|
|
264
|
+
{ ...base, fn: "gate:prod", status: "ok", approval: { gate: "prod", resolvedBy: "a", timestamp: "2026-10-16T11:00:00.000Z" } },
|
|
265
|
+
{ ...base, fn: "gate:prod", status: "skipped", refusal: "r" },
|
|
266
|
+
{ ...base, fn: "build", status: "skipped", resumed: { run: "r-1", gate: "prod" } },
|
|
267
|
+
{ ...base, fn: "migrate", status: "skipped" },
|
|
268
|
+
];
|
|
269
|
+
for (const r of records) for (const b of runEventsOfStepRecord(r, { ...ctx, component: "api" })) writer.emit(b);
|
|
270
|
+
const evs = events();
|
|
271
|
+
expect(evs.length).toBe(8);
|
|
272
|
+
for (const e of evs) {
|
|
273
|
+
expectValid(e);
|
|
274
|
+
expect(e).toMatchObject({ component: "api" });
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
});
|