@intentius/chant 0.124.27 → 0.124.29
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/build-params.d.ts +34 -1
- package/dist/build-params.d.ts.map +1 -1
- package/dist/build-params.js +31 -1
- package/dist/change-set-graph.d.ts +63 -0
- package/dist/change-set-graph.d.ts.map +1 -0
- package/dist/change-set-graph.js +130 -0
- package/dist/change-set.d.ts +62 -3
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +36 -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/build-params-cli.d.ts +7 -1
- package/dist/cli/build-params-cli.d.ts.map +1 -1
- package/dist/cli/build-params-cli.js +17 -8
- 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 +15 -11
- 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 +64 -4
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +43 -4
- 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 +10 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.js +54 -6
- package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
- package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
- package/dist/cli/handlers/lifecycle-gc.js +143 -0
- package/dist/cli/handlers/pr.d.ts +16 -2
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +269 -37
- package/dist/cli/handlers/promote.d.ts +7 -0
- package/dist/cli/handlers/promote.d.ts.map +1 -1
- package/dist/cli/handlers/promote.js +440 -28
- package/dist/cli/handlers/release.d.ts +91 -0
- package/dist/cli/handlers/release.d.ts.map +1 -0
- package/dist/cli/handlers/release.js +589 -0
- package/dist/cli/handlers/run.d.ts +38 -2
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/run.js +234 -16
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +143 -17
- 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 +73 -5
- package/dist/cli/registry.d.ts +43 -5
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/cli/run-events.d.ts +30 -0
- package/dist/cli/run-events.d.ts.map +1 -0
- package/dist/cli/run-events.js +49 -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 +6 -1
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/auto-release.js +3 -0
- package/dist/components/capability.d.ts +9 -0
- package/dist/components/capability.d.ts.map +1 -1
- 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 +50 -9
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/driver.js +116 -54
- package/dist/components/fan-out-inputs.d.ts +69 -0
- package/dist/components/fan-out-inputs.d.ts.map +1 -0
- package/dist/components/fan-out-inputs.js +169 -0
- package/dist/components/fan-out-record.d.ts +13 -0
- package/dist/components/fan-out-record.d.ts.map +1 -1
- package/dist/components/fan-out-record.js +16 -0
- 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 +85 -26
- package/dist/components/fan-out.d.ts +4 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/pr-pipeline.d.ts +25 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +22 -4
- 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 +38 -2
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/promote.js +77 -2
- package/dist/components/release-compile.d.ts +142 -0
- package/dist/components/release-compile.d.ts.map +1 -0
- package/dist/components/release-compile.js +382 -0
- package/dist/components/release-gate.d.ts +135 -0
- package/dist/components/release-gate.d.ts.map +1 -0
- package/dist/components/release-gate.js +288 -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 +8 -1
- package/dist/components/wave-plan.d.ts.map +1 -1
- package/dist/components/wave-plan.js +11 -3
- package/dist/config.d.ts +45 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/flag-params.d.ts +54 -0
- package/dist/flag-params.d.ts.map +1 -0
- package/dist/flag-params.js +130 -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/__fixtures__/publish-fakes.d.ts +120 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
- package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
- package/dist/lifecycle/change-set-store.d.ts +68 -0
- package/dist/lifecycle/change-set-store.d.ts.map +1 -0
- package/dist/lifecycle/change-set-store.js +97 -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/delivery-metrics.d.ts +139 -0
- package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
- package/dist/lifecycle/delivery-metrics.js +145 -0
- package/dist/lifecycle/deploy-outcome.d.ts +81 -0
- package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
- package/dist/lifecycle/deploy-outcome.js +101 -0
- package/dist/lifecycle/gate-change-set.d.ts +78 -0
- package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
- package/dist/lifecycle/gate-change-set.js +138 -0
- package/dist/lifecycle/gate-ledger.d.ts +51 -4
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.js +18 -0
- package/dist/lifecycle/git.d.ts +30 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/git.js +31 -5
- package/dist/lifecycle/index.d.ts +4 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +4 -0
- package/dist/lifecycle/mixed-environment.d.ts +95 -0
- package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
- package/dist/lifecycle/mixed-environment.js +154 -0
- package/dist/lifecycle/publish-attachments.d.ts +79 -0
- package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
- package/dist/lifecycle/publish-attachments.js +179 -0
- package/dist/lifecycle/publish-credentials.d.ts +37 -0
- package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
- package/dist/lifecycle/publish-credentials.js +69 -0
- package/dist/lifecycle/publish-declaration.d.ts +97 -0
- package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
- package/dist/lifecycle/publish-declaration.js +83 -0
- package/dist/lifecycle/publish-forge.d.ts +53 -0
- package/dist/lifecycle/publish-forge.d.ts.map +1 -0
- package/dist/lifecycle/publish-forge.js +298 -0
- package/dist/lifecycle/publish-npm.d.ts +61 -0
- package/dist/lifecycle/publish-npm.d.ts.map +1 -0
- package/dist/lifecycle/publish-npm.js +242 -0
- package/dist/lifecycle/publish-oci.d.ts +64 -0
- package/dist/lifecycle/publish-oci.d.ts.map +1 -0
- package/dist/lifecycle/publish-oci.js +383 -0
- package/dist/lifecycle/publish-record.d.ts +66 -0
- package/dist/lifecycle/publish-record.d.ts.map +1 -0
- package/dist/lifecycle/publish-record.js +84 -0
- package/dist/lifecycle/publish-targets.d.ts +45 -0
- package/dist/lifecycle/publish-targets.d.ts.map +1 -0
- package/dist/lifecycle/publish-targets.js +85 -0
- package/dist/lifecycle/release-cut.d.ts +154 -0
- package/dist/lifecycle/release-cut.d.ts.map +1 -0
- package/dist/lifecycle/release-cut.js +366 -0
- package/dist/lifecycle/release-join.d.ts +95 -0
- package/dist/lifecycle/release-join.d.ts.map +1 -0
- package/dist/lifecycle/release-join.js +139 -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-publish.d.ts +312 -0
- package/dist/lifecycle/release-publish.d.ts.map +1 -0
- package/dist/lifecycle/release-publish.js +335 -0
- package/dist/lifecycle/release-revocation.d.ts +80 -0
- package/dist/lifecycle/release-revocation.d.ts.map +1 -0
- package/dist/lifecycle/release-revocation.js +123 -0
- package/dist/lifecycle/release-store.d.ts +200 -0
- package/dist/lifecycle/release-store.d.ts.map +1 -0
- package/dist/lifecycle/release-store.js +320 -0
- package/dist/lifecycle/release-verify.d.ts +61 -0
- package/dist/lifecycle/release-verify.d.ts.map +1 -0
- package/dist/lifecycle/release-verify.js +92 -0
- package/dist/lifecycle/release-workstation.d.ts +91 -0
- package/dist/lifecycle/release-workstation.d.ts.map +1 -0
- package/dist/lifecycle/release-workstation.js +180 -0
- package/dist/lifecycle/retention-policy.d.ts +45 -0
- package/dist/lifecycle/retention-policy.d.ts.map +1 -0
- package/dist/lifecycle/retention-policy.js +43 -0
- package/dist/lifecycle/retention.d.ts +185 -0
- package/dist/lifecycle/retention.d.ts.map +1 -0
- package/dist/lifecycle/retention.js +400 -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/plan-summary.d.ts.map +1 -1
- package/dist/plan-summary.js +8 -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/pr-loop.d.ts +7 -0
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +23 -4
- package/dist/pr-report-url.d.ts +82 -0
- package/dist/pr-report-url.d.ts.map +1 -0
- package/dist/pr-report-url.js +174 -0
- package/dist/provenance.d.ts +2 -2
- package/dist/provenance.d.ts.map +1 -1
- package/dist/run-event-store.d.ts +104 -0
- package/dist/run-event-store.d.ts.map +1 -0
- package/dist/run-event-store.js +251 -0
- package/dist/run-event-stream.d.ts +133 -0
- package/dist/run-event-stream.d.ts.map +1 -0
- package/dist/run-event-stream.js +242 -0
- package/dist/run-event.d.ts +285 -0
- package/dist/run-event.d.ts.map +1 -0
- package/dist/run-event.js +209 -0
- package/dist/wave-runtime.mjs +262 -226
- package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
- package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
- package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
- 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 +32 -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/check.schema.json +4 -2
- package/dist/workspace/checks/records.d.ts +9 -1
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks/records.js +35 -1
- package/dist/workspace/checks.d.ts +1 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +2 -1
- 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 +281 -13
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +456 -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 +35 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +27 -1
- package/dist/workspace/declaration.schema.json +76 -0
- package/dist/workspace/declared-kinds.d.ts +16 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -1
- package/dist/workspace/declared-kinds.js +25 -0
- package/dist/workspace/delivery-metrics.d.ts +91 -0
- package/dist/workspace/delivery-metrics.d.ts.map +1 -0
- package/dist/workspace/delivery-metrics.js +217 -0
- package/dist/workspace/delivery-metrics.schema.json +519 -0
- package/dist/workspace/flags-read.d.ts +37 -0
- package/dist/workspace/flags-read.d.ts.map +1 -0
- package/dist/workspace/flags-read.js +103 -0
- package/dist/workspace/flags.d.ts +100 -0
- package/dist/workspace/flags.d.ts.map +1 -0
- package/dist/workspace/flags.js +98 -0
- package/dist/workspace/graph-cli.js +1 -1
- 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-cli.d.ts +11 -2
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-cli.js +54 -5
- package/dist/workspace/intent-trace.d.ts +219 -0
- package/dist/workspace/intent-trace.d.ts.map +1 -0
- package/dist/workspace/intent-trace.js +314 -0
- package/dist/workspace/intent.d.ts +36 -7
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +192 -22
- package/dist/workspace/intent.schema.json +702 -6
- 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/overlap.d.ts +146 -0
- package/dist/workspace/overlap.d.ts.map +1 -0
- package/dist/workspace/overlap.js +276 -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 +4 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +7 -1
- package/dist/workspace/records.d.ts +15 -2
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/records.js +32 -0
- package/dist/workspace/records.schema.json +21 -2
- package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
- package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
- package/dist/workspace/releases.d.ts +81 -0
- package/dist/workspace/releases.d.ts.map +1 -0
- package/dist/workspace/releases.js +103 -0
- package/dist/workspace/releases.schema.json +513 -0
- package/dist/workspace/run-event.schema.json +295 -0
- package/dist/workspace/status-gates.d.ts +34 -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 +117 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +218 -4
- package/dist/workspace/status.schema.json +268 -2
- 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/dist/workspace/wip.d.ts.map +1 -1
- package/dist/workspace/wip.js +11 -3
- 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/build-params.test.ts +46 -0
- package/src/build-params.ts +55 -1
- package/src/change-set-graph.test.ts +72 -0
- package/src/change-set-graph.ts +141 -0
- package/src/change-set.ts +87 -6
- package/src/chant-version.ts +16 -0
- package/src/cli/build-params-cli.ts +22 -9
- package/src/cli/commands/build.ts +25 -11
- 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 +84 -0
- package/src/cli/handlers/approve-revoked.test.ts +155 -0
- package/src/cli/handlers/approve.ts +65 -2
- package/src/cli/handlers/build.test.ts +67 -0
- package/src/cli/handlers/build.ts +46 -4
- package/src/cli/handlers/change-set.ts +49 -1
- package/src/cli/handlers/fan-out.test.ts +228 -1
- package/src/cli/handlers/fan-out.ts +53 -6
- package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
- package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
- package/src/cli/handlers/lifecycle-gc.ts +146 -0
- package/src/cli/handlers/operator.test.ts +37 -0
- package/src/cli/handlers/pr.test.ts +341 -0
- package/src/cli/handlers/pr.ts +292 -47
- package/src/cli/handlers/promote.test.ts +669 -7
- package/src/cli/handlers/promote.ts +464 -29
- package/src/cli/handlers/release-implicit.e2e.test.ts +161 -0
- package/src/cli/handlers/release-join.e2e.test.ts +185 -0
- package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
- package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
- package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
- package/src/cli/handlers/release.e2e.test.ts +199 -0
- package/src/cli/handlers/release.ts +593 -0
- package/src/cli/handlers/run-auto-release.test.ts +80 -0
- package/src/cli/handlers/run-events-cmd.test.ts +92 -0
- package/src/cli/handlers/run-release.test.ts +55 -0
- package/src/cli/handlers/run.test.ts +30 -0
- package/src/cli/handlers/run.ts +243 -18
- package/src/cli/main.test.ts +8 -0
- package/src/cli/main.ts +143 -17
- 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 +56 -5
- package/src/cli/registry.ts +43 -5
- package/src/cli/run-events.ts +57 -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 +30 -0
- package/src/components/auto-release.ts +9 -1
- package/src/components/capability.ts +9 -0
- package/src/components/cli-support.test.ts +82 -0
- package/src/components/cli-support.ts +16 -9
- package/src/components/driver.test.ts +24 -0
- package/src/components/driver.ts +165 -56
- package/src/components/fan-out-inputs.test.ts +85 -0
- package/src/components/fan-out-inputs.ts +192 -0
- package/src/components/fan-out-record.test.ts +10 -0
- package/src/components/fan-out-record.ts +19 -0
- package/src/components/fan-out-run.ts +104 -35
- package/src/components/fan-out.ts +4 -0
- package/src/components/pr-pipeline.ts +35 -4
- 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 +94 -3
- package/src/components/release-compile.test.ts +147 -0
- package/src/components/release-compile.ts +459 -0
- package/src/components/release-gate.test.ts +215 -0
- package/src/components/release-gate.ts +355 -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 +16 -3
- package/src/config.ts +15 -1
- package/src/flag-params.test.ts +144 -0
- package/src/flag-params.ts +134 -0
- package/src/index.ts +2 -0
- package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
- package/src/lifecycle/change-set-store.test.ts +217 -0
- package/src/lifecycle/change-set-store.ts +113 -0
- package/src/lifecycle/commit-sha.ts +23 -0
- package/src/lifecycle/delivery-metrics.ts +263 -0
- package/src/lifecycle/deploy-outcome.ts +147 -0
- package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
- package/src/lifecycle/gate-change-set.ts +164 -0
- package/src/lifecycle/gate-ledger.test.ts +33 -0
- package/src/lifecycle/gate-ledger.ts +68 -3
- package/src/lifecycle/git.ts +61 -8
- package/src/lifecycle/index.ts +4 -0
- package/src/lifecycle/mixed-environment.test.ts +97 -0
- package/src/lifecycle/mixed-environment.ts +204 -0
- package/src/lifecycle/publish-attachments.ts +196 -0
- package/src/lifecycle/publish-credentials.ts +84 -0
- package/src/lifecycle/publish-declaration.ts +95 -0
- package/src/lifecycle/publish-forge.ts +325 -0
- package/src/lifecycle/publish-npm.ts +259 -0
- package/src/lifecycle/publish-oci.ts +396 -0
- package/src/lifecycle/publish-record.ts +113 -0
- package/src/lifecycle/publish-resume.test.ts +168 -0
- package/src/lifecycle/publish-targets.test.ts +494 -0
- package/src/lifecycle/publish-targets.ts +100 -0
- package/src/lifecycle/release-cut-signed.test.ts +180 -0
- package/src/lifecycle/release-cut.test.ts +483 -0
- package/src/lifecycle/release-cut.ts +503 -0
- package/src/lifecycle/release-flag.test.ts +112 -0
- package/src/lifecycle/release-join.test.ts +68 -0
- package/src/lifecycle/release-join.ts +182 -0
- package/src/lifecycle/release-ledger.test.ts +14 -0
- package/src/lifecycle/release-ledger.ts +85 -17
- package/src/lifecycle/release-publish.test.ts +225 -0
- package/src/lifecycle/release-publish.ts +568 -0
- package/src/lifecycle/release-revocation.ts +147 -0
- package/src/lifecycle/release-store.ts +434 -0
- package/src/lifecycle/release-verify.test.ts +110 -0
- package/src/lifecycle/release-verify.ts +120 -0
- package/src/lifecycle/release-workstation.test.ts +204 -0
- package/src/lifecycle/release-workstation.ts +216 -0
- package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
- package/src/lifecycle/retention-policy.ts +63 -0
- package/src/lifecycle/retention.test.ts +363 -0
- package/src/lifecycle/retention.ts +508 -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/plan-summary.test.ts +20 -0
- package/src/plan-summary.ts +8 -1
- package/src/point-decision.ts +12 -2
- package/src/pr-loop.ts +25 -4
- package/src/pr-report-url.test.ts +131 -0
- package/src/pr-report-url.ts +209 -0
- package/src/provenance.ts +2 -2
- package/src/run-event-store.test.ts +144 -0
- package/src/run-event-store.ts +273 -0
- package/src/run-event-stream.test.ts +175 -0
- package/src/run-event-stream.ts +309 -0
- package/src/run-event.test.ts +277 -0
- package/src/run-event.ts +471 -0
- package/src/wave-loop-golden.test.ts +9 -0
- package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
- package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
- package/src/workspace/__fixtures__/older-records/older-records.json +326 -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 +32 -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/check.schema.json +4 -2
- package/src/workspace/checks/records.ts +35 -1
- package/src/workspace/checks.ts +2 -1
- 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 +635 -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 +76 -0
- package/src/workspace/declaration.test.ts +28 -0
- package/src/workspace/declaration.ts +60 -2
- package/src/workspace/declared-kinds.ts +40 -1
- package/src/workspace/delivery-metrics.schema.json +519 -0
- package/src/workspace/delivery-metrics.test.ts +236 -0
- package/src/workspace/delivery-metrics.ts +286 -0
- package/src/workspace/flags-read.ts +121 -0
- package/src/workspace/flags.test.ts +141 -0
- package/src/workspace/flags.ts +157 -0
- package/src/workspace/graph-cli.ts +1 -1
- package/src/workspace/identity-gate-rule.ts +91 -7
- package/src/workspace/identity.ts +14 -1
- package/src/workspace/intent-cli.ts +46 -5
- package/src/workspace/intent-trace.test.ts +173 -0
- package/src/workspace/intent-trace.ts +484 -0
- package/src/workspace/intent.schema.json +702 -6
- package/src/workspace/intent.ts +196 -26
- 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/older-records.test.ts +412 -0
- package/src/workspace/overlap.test.ts +165 -0
- package/src/workspace/overlap.ts +340 -0
- package/src/workspace/promotion-order.ts +97 -0
- package/src/workspace/read-contract.e2e.test.ts +5 -1
- package/src/workspace/reason-codes.test.ts +7 -1
- package/src/workspace/reason-codes.ts +7 -1
- package/src/workspace/record-assets.test.ts +2 -1
- package/src/workspace/records-contract.e2e.test.ts +2 -1
- package/src/workspace/records.schema.json +21 -2
- package/src/workspace/records.ts +36 -2
- package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
- package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
- package/src/workspace/releases.schema.json +513 -0
- package/src/workspace/releases.test.ts +108 -0
- package/src/workspace/releases.ts +133 -0
- package/src/workspace/run-event.schema.json +295 -0
- package/src/workspace/status-contract.test.ts +81 -1
- package/src/workspace/status-gates.ts +64 -1
- package/src/workspace/status-mixed.test.ts +152 -0
- package/src/workspace/status-revoked.test.ts +153 -0
- package/src/workspace/status-stewards.ts +6 -0
- package/src/workspace/status.schema.json +268 -2
- package/src/workspace/status.ts +334 -4
- 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/workspace/wip.ts +11 -2
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4041: the local executor's run-event stream (`chant run <op>
|
|
3
|
+
* --events-json`). Every line passes the schema; each step's start comes
|
|
4
|
+
* before its settle under the same step id; and the cases an old
|
|
5
|
+
* `StepRecord` could not tell apart come out as `gated`, `skip: "receipt"`
|
|
6
|
+
* and `skip: "not-run"`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test, vi } from "vitest";
|
|
10
|
+
import type { ActivityFn, ActivityProfile } from "./activity-registry";
|
|
11
|
+
import type { OpConfig } from "./types";
|
|
12
|
+
import { OpRunFailure, runOpLocally, type RunOpOptions } from "./local-executor";
|
|
13
|
+
import { memoryGateLedgerPort } from "./gate";
|
|
14
|
+
import { receiptActivities, type ReceiptStore } from "./receipt-store";
|
|
15
|
+
import { EffectReceipt, receiptExpectation } from "../effect-receipt";
|
|
16
|
+
import { effect } from "./builders";
|
|
17
|
+
import type { RunEvent } from "../run-event";
|
|
18
|
+
import { checkRunEventLines, stepKey, stepTrail } from "../workspace/__fixtures__/run-event-check";
|
|
19
|
+
|
|
20
|
+
vi.mock("./gate", async () => {
|
|
21
|
+
const actual = await vi.importActual<typeof import("./gate")>("./gate");
|
|
22
|
+
return { ...actual, gateIsSealed: async () => false };
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const PROFILES: Record<string, ActivityProfile> = { fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } } };
|
|
26
|
+
const NOW = "2026-10-16T12:00:00.000Z";
|
|
27
|
+
const SHA = "0123456789abcdef0123456789abcdef01234567";
|
|
28
|
+
|
|
29
|
+
const op = (partial: Partial<OpConfig>): OpConfig => ({ name: "deploy-api", overview: "", phases: [], ...partial });
|
|
30
|
+
const ok: ActivityFn = async () => ({ done: true });
|
|
31
|
+
|
|
32
|
+
/** Run an Op with its events captured, settling whether it resolves or rejects. */
|
|
33
|
+
async function run(config: OpConfig, activities: Map<string, ActivityFn>, options: RunOpOptions = {}) {
|
|
34
|
+
const lines: string[] = [];
|
|
35
|
+
let status: string;
|
|
36
|
+
try {
|
|
37
|
+
status = (await runOpLocally(config, activities, PROFILES, undefined, {
|
|
38
|
+
gates: memoryGateLedgerPort(),
|
|
39
|
+
now: NOW,
|
|
40
|
+
runId: "local-1",
|
|
41
|
+
commit: SHA,
|
|
42
|
+
runEvents: { write: (l) => lines.push(l) },
|
|
43
|
+
...options,
|
|
44
|
+
})).status;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (!(err instanceof OpRunFailure)) throw err;
|
|
47
|
+
status = err.result.status;
|
|
48
|
+
}
|
|
49
|
+
return { status, events: checkRunEventLines(lines) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("the local runtime's run events", () => {
|
|
53
|
+
test("a run that passes: run, phases and steps, each step started before it settles, named as the run record names it", async () => {
|
|
54
|
+
const { status, events } = await run(
|
|
55
|
+
op({ phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }] }, { name: "Ship", steps: [{ kind: "activity", fn: "push" }] }] }),
|
|
56
|
+
new Map([["make", ok], ["push", ok]]),
|
|
57
|
+
);
|
|
58
|
+
expect(status).toBe("ok");
|
|
59
|
+
// In a CI job the run keeps its own id and names the CI run as its origin, as its run record does (#4020).
|
|
60
|
+
const ci = Boolean(process.env.GITHUB_RUN_ID || process.env.CI_PIPELINE_ID);
|
|
61
|
+
expect(events[0]).toMatchObject({ runId: "local-1", kind: "op", name: "deploy-api", gitSha: SHA, runtime: ci ? "ci" : "local" });
|
|
62
|
+
expect(events[0]!.runOrigin.forge).toBe(process.env.GITHUB_RUN_ID ? "github" : process.env.CI_PIPELINE_ID ? "gitlab" : "local");
|
|
63
|
+
expect(events.map((e) => e.type)).toEqual([
|
|
64
|
+
"run-start",
|
|
65
|
+
"phase-start", "step-start", "step-settle", "phase-settle",
|
|
66
|
+
"phase-start", "step-start", "step-settle", "phase-settle",
|
|
67
|
+
"run-settle",
|
|
68
|
+
]);
|
|
69
|
+
expect(events.at(-1)).toMatchObject({ status: "ok" });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("a step's start is emitted before its settle, with the same step id", async () => {
|
|
73
|
+
const { events } = await run(
|
|
74
|
+
op({ phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }, { kind: "activity", fn: "make" }] }] }),
|
|
75
|
+
new Map([["make", ok]]),
|
|
76
|
+
);
|
|
77
|
+
const steps = events.filter((e): e is Extract<RunEvent, { type: "step-start" | "step-settle" }> => e.type === "step-start" || e.type === "step-settle");
|
|
78
|
+
// Two steps of one phase share a name; the index keeps them apart.
|
|
79
|
+
expect(steps.map((e) => [e.type, stepKey(e)])).toEqual([
|
|
80
|
+
["step-start", JSON.stringify([null, "Build", "make", 0])],
|
|
81
|
+
["step-settle", JSON.stringify([null, "Build", "make", 0])],
|
|
82
|
+
["step-start", JSON.stringify([null, "Build", "make", 1])],
|
|
83
|
+
["step-settle", JSON.stringify([null, "Build", "make", 1])],
|
|
84
|
+
]);
|
|
85
|
+
expect(steps[0]!.seq).toBeLessThan(steps[1]!.seq);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("a parallel phase starts each step before any settles, and tells two steps of one name apart by index", async () => {
|
|
89
|
+
const { events } = await run(
|
|
90
|
+
op({ phases: [{ name: "Fan", parallel: true, steps: [{ kind: "activity", fn: "probe", args: { at: 1 } }, { kind: "activity", fn: "probe", args: { at: 2 } }] }] }),
|
|
91
|
+
new Map([["probe", ok]]),
|
|
92
|
+
);
|
|
93
|
+
expect(stepTrail(events)).toEqual([
|
|
94
|
+
["step-start", "probe"], ["step-start", "probe"], ["step-settle", "probe", "ok"], ["step-settle", "probe", "ok"],
|
|
95
|
+
]);
|
|
96
|
+
expect(events.filter((e) => e.type === "step-settle").map((e) => (e as { index?: number }).index)).toEqual([0, 1]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("an unapproved gate settles gated after gate-waiting; the steps after it settle skipped as not-run", async () => {
|
|
100
|
+
const { status, events } = await run(
|
|
101
|
+
op({ phases: [{ name: "Ship", steps: [{ kind: "activity", fn: "before" }, { kind: "gate", gate: "approve-prod" }, { kind: "activity", fn: "after" }] }] }),
|
|
102
|
+
new Map([["before", ok], ["after", ok]]),
|
|
103
|
+
);
|
|
104
|
+
expect(status).toBe("gated");
|
|
105
|
+
expect(stepTrail(events)).toEqual([
|
|
106
|
+
["step-start", "before"], ["step-settle", "before", "ok"],
|
|
107
|
+
["step-start", "gate:approve-prod"], ["gate-waiting", "gate:approve-prod"], ["step-settle", "gate:approve-prod", "gated"],
|
|
108
|
+
["step-settle", "after", "skipped:not-run"],
|
|
109
|
+
]);
|
|
110
|
+
expect(events.find((e) => e.type === "gate-waiting")).toMatchObject({ op: "deploy-api", gate: "approve-prod", point: null, index: 1 });
|
|
111
|
+
expect(events.find((e) => e.type === "phase-settle")).toMatchObject({ status: "gated" });
|
|
112
|
+
expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "gated" });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("an approved gate resolves, naming who approved it", async () => {
|
|
116
|
+
const gates = memoryGateLedgerPort({
|
|
117
|
+
resolutions: [{ version: 1, op: "deploy-api", gate: "approve-prod", resolvedBy: "alex", timestamp: "2026-10-16T11:00:00.000Z" }],
|
|
118
|
+
});
|
|
119
|
+
const { status, events } = await run(op({ phases: [{ name: "Ship", steps: [{ kind: "gate", gate: "approve-prod" }] }] }), new Map(), { gates });
|
|
120
|
+
expect(status).toBe("ok");
|
|
121
|
+
expect(events.find((e) => e.type === "gate-resolved")).toMatchObject({ gate: "approve-prod", resolvedBy: "alex", via: null, index: 0 });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("a failed step fails its phase and the run, with its error, and the rest of its phase is not-run", async () => {
|
|
125
|
+
const boom: ActivityFn = async () => { throw new Error("disk full"); };
|
|
126
|
+
const { status, events } = await run(
|
|
127
|
+
op({ phases: [{ name: "Build", steps: [{ kind: "activity", fn: "make" }, { kind: "activity", fn: "pack" }] }] }),
|
|
128
|
+
new Map([["make", boom], ["pack", ok]]),
|
|
129
|
+
);
|
|
130
|
+
expect(status).toBe("fail");
|
|
131
|
+
expect(stepTrail(events)).toEqual([["step-start", "make"], ["step-settle", "make", "failed"], ["step-settle", "pack", "skipped:not-run"]]);
|
|
132
|
+
expect(events.find((e) => e.type === "phase-settle")).toMatchObject({ status: "failed" });
|
|
133
|
+
expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "failed", error: "disk full" });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("an effect whose receipt matched settles its steps skipped with skip: receipt", async () => {
|
|
137
|
+
const seeded = EffectReceipt("seeded", { effect: "db-seed", flavor: "hash", inputs: { version: 3 } });
|
|
138
|
+
const values = new Map<string, string>([["seeded", receiptExpectation(seeded)]]);
|
|
139
|
+
const store: ReceiptStore = {
|
|
140
|
+
read: async (r) => values.get(r.name),
|
|
141
|
+
write: async (r, e) => void values.set(r.name, e),
|
|
142
|
+
};
|
|
143
|
+
const { receiptRead, receiptWrite } = receiptActivities(store);
|
|
144
|
+
const { status, events } = await run(
|
|
145
|
+
op({ phases: [{ name: "Seed", steps: [effect(seeded, [{ kind: "activity", fn: "seed" }])] }] }),
|
|
146
|
+
new Map<string, ActivityFn>([["seed", ok], ["receiptRead", receiptRead as unknown as ActivityFn], ["receiptWrite", receiptWrite as unknown as ActivityFn]]),
|
|
147
|
+
);
|
|
148
|
+
expect(status).toBe("ok");
|
|
149
|
+
expect(stepTrail(events)).toEqual([
|
|
150
|
+
["step-start", "receiptRead"], ["step-settle", "receiptRead", "ok"],
|
|
151
|
+
["step-settle", "seed", "skipped:receipt"],
|
|
152
|
+
]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("a run that throws before its first phase still ends its stream, failed with the reason", async () => {
|
|
156
|
+
const lines: string[] = [];
|
|
157
|
+
await expect(
|
|
158
|
+
runOpLocally(op({ phases: [{ name: "P", parallel: true, steps: [effect(EffectReceipt("r", { effect: "e", flavor: "existence", inputs: {} }), [])] }] }), new Map(), PROFILES, undefined, {
|
|
159
|
+
gates: memoryGateLedgerPort(), now: NOW, commit: SHA, runEvents: { write: (l) => lines.push(l) },
|
|
160
|
+
}),
|
|
161
|
+
).rejects.toThrow(/cannot run in a parallel phase/);
|
|
162
|
+
const events = checkRunEventLines(lines);
|
|
163
|
+
expect(events.map((e) => e.type)).toEqual(["run-start", "run-settle"]);
|
|
164
|
+
expect(events.at(-1)).toMatchObject({ status: "failed", error: expect.stringMatching(/cannot run in a parallel phase/) });
|
|
165
|
+
});
|
|
166
|
+
});
|
package/src/op/run-live.ts
CHANGED
|
@@ -86,7 +86,7 @@ function keyOf(prefix: string, env: string, op: string): string {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/** The checkout's shared git directory (the same for every worktree), or null outside a checkout. */
|
|
89
|
-
async function gitCommonDir(cwd: string): Promise<string | null> {
|
|
89
|
+
export async function gitCommonDir(cwd: string): Promise<string | null> {
|
|
90
90
|
try {
|
|
91
91
|
const { stdout } = await execFileAsync("git", ["rev-parse", "--git-common-dir"], { cwd });
|
|
92
92
|
const dir = stdout.trim();
|
package/src/op/runtime.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type { OpConfig } from "./types";
|
|
|
20
20
|
import type { StepRecord, OpRunResult } from "./local-executor";
|
|
21
21
|
import type { WaitingPoint } from "./steward-points";
|
|
22
22
|
import type { GateResolutionRecord } from "../lifecycle/gate-ledger";
|
|
23
|
+
import type { RunOrigin } from "../lifecycle/release-ledger";
|
|
23
24
|
import type { RunComponentsOptions, RunComponentsResult } from "../components/cli-support";
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -159,6 +160,29 @@ export interface OpRunRecord {
|
|
|
159
160
|
* first phase.
|
|
160
161
|
*/
|
|
161
162
|
resumed?: { gate: string; run: string; checkpoint: string };
|
|
163
|
+
/**
|
|
164
|
+
* The commit the run built from (#4020): the checkout's `HEAD`, else the
|
|
165
|
+
* commit the CI job started for. Gate facts the run writes carry the same
|
|
166
|
+
* commit, so a run and its gate join on it. Absent on a record written
|
|
167
|
+
* before 0.124.28 and when the run knew no commit.
|
|
168
|
+
*/
|
|
169
|
+
gitSha?: string;
|
|
170
|
+
/**
|
|
171
|
+
* The CI run this Op ran in (#4020), as a deploy record's `runOrigin` names
|
|
172
|
+
* its run, so the two name a run the same way. `id` is still the Op's own
|
|
173
|
+
* run id. Absent outside CI and on a record written before 0.124.28.
|
|
174
|
+
*/
|
|
175
|
+
runOrigin?: RunOrigin;
|
|
176
|
+
/**
|
|
177
|
+
* The run's statement (#4044): a DSSE envelope over an in-toto statement
|
|
178
|
+
* (`https://intentius.io/chant/op-run/v1`) a runner key signed at the end
|
|
179
|
+
* of the run, naming the run, its commit and the SHA-256 of this record
|
|
180
|
+
* without the statement (`../workspace/trust/op-run-statement.ts`).
|
|
181
|
+
* `chant ci tick` with `ci.green.source: "runs"` counts only a run that
|
|
182
|
+
* carries one. Absent on a run given no runner key, and on a record
|
|
183
|
+
* written before 0.124.28.
|
|
184
|
+
*/
|
|
185
|
+
statement?: { payloadType: string; payload: string; signatures: { keyid: string; sig: string }[] };
|
|
162
186
|
}
|
|
163
187
|
|
|
164
188
|
/** An {@link OpRunRecord} before the ledger stamps its version and mints an id. */
|
|
@@ -187,6 +211,14 @@ export interface OpRunStartOptions {
|
|
|
187
211
|
profile?: string;
|
|
188
212
|
/** Called once per settled step, so `--progress-json` can stream. */
|
|
189
213
|
progress?: (record: StepRecord) => void;
|
|
214
|
+
/**
|
|
215
|
+
* Where the run's event stream goes (#4041, `--events-json`): one
|
|
216
|
+
* `run-event.schema.json` line at a time, written through the one writer
|
|
217
|
+
* (`createRunEventWriter`). The local runtime writes `runtime: "local"`
|
|
218
|
+
* events (`ci` in a CI job); a hosted runtime writes its own, as fountain
|
|
219
|
+
* writes `runtime: "steward"` ones. A runtime that cannot stream omits it.
|
|
220
|
+
*/
|
|
221
|
+
runEvents?: (line: string) => void;
|
|
190
222
|
/** Aborts in-flight work (Ctrl-C). */
|
|
191
223
|
signal?: AbortSignal;
|
|
192
224
|
/**
|
|
@@ -205,6 +237,13 @@ export interface OpRunStartOptions {
|
|
|
205
237
|
* and the run `chant run status --on fountain` reports carry the same id.
|
|
206
238
|
*/
|
|
207
239
|
runId?: string;
|
|
240
|
+
/**
|
|
241
|
+
* Signs the run's record when it settles (#4044), for `chant run <op>
|
|
242
|
+
* --key` or `CHANT_RUNNER_KEY`: the local runtime stores what it returns
|
|
243
|
+
* on the record as `statement`. A runtime that writes no record here
|
|
244
|
+
* ignores it.
|
|
245
|
+
*/
|
|
246
|
+
signRecord?: (record: OpRunRecord) => NonNullable<OpRunRecord["statement"]>;
|
|
208
247
|
}
|
|
209
248
|
|
|
210
249
|
/** What a caller hands {@link OpRuntimeProvider.resolveGate} besides the resolution. */
|
|
@@ -51,6 +51,7 @@ vi.mock("../../lifecycle/run-ledger", async () => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
const { createLocalOpRuntime } = await import("./local");
|
|
54
|
+
const { checkRunEventLines, stepTrail } = await import("../../workspace/__fixtures__/run-event-check");
|
|
54
55
|
|
|
55
56
|
function op(name: string, steps: unknown[]): OpConfig {
|
|
56
57
|
return {
|
|
@@ -145,6 +146,21 @@ describe("the local op runtime", () => {
|
|
|
145
146
|
expect(seen).toEqual(["ok", "ok"]);
|
|
146
147
|
});
|
|
147
148
|
|
|
149
|
+
test("runEvents gets the run's event stream, named by the handle's run id (#4041)", async () => {
|
|
150
|
+
loadActivitiesMock.mockResolvedValue(new Map([["ok", vi.fn(async () => ({}))]]));
|
|
151
|
+
const runtime = createLocalOpRuntime();
|
|
152
|
+
const lines: string[] = [];
|
|
153
|
+
|
|
154
|
+
const handle = await runtime.start(op("hello", [{ kind: "activity", fn: "ok", args: {} }]), { runEvents: (l) => lines.push(l) });
|
|
155
|
+
await handle.result();
|
|
156
|
+
|
|
157
|
+
const events = checkRunEventLines(lines);
|
|
158
|
+
expect(events[0]).toMatchObject({ type: "run-start", runId: handle.runId, kind: "op", name: "hello" });
|
|
159
|
+
expect(events[0]!.runtime).toBe(process.env.GITHUB_RUN_ID || process.env.CI_PIPELINE_ID ? "ci" : "local");
|
|
160
|
+
expect(stepTrail(events)).toEqual([["step-start", "ok"], ["step-settle", "ok", "ok"]]);
|
|
161
|
+
expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "ok" });
|
|
162
|
+
});
|
|
163
|
+
|
|
148
164
|
test("a gated Op settles as gated, naming the gate, and runs nothing after it (#2119)", async () => {
|
|
149
165
|
const after = vi.fn(async () => ({}));
|
|
150
166
|
loadActivitiesMock.mockResolvedValue(new Map([["after", after]]));
|
package/src/op/runtimes/local.ts
CHANGED
|
@@ -176,6 +176,15 @@ export function createLocalOpRuntime(opts: { projectPath?: string } = {}): OpRun
|
|
|
176
176
|
`appended to the run ledger: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
177
177
|
),
|
|
178
178
|
...(startOpts.progress ? { onRecord: startOpts.progress } : {}),
|
|
179
|
+
...(startOpts.runEvents ? { runEvents: { write: startOpts.runEvents } } : {}),
|
|
180
|
+
// #4044: a runner key signs the record as the run settles.
|
|
181
|
+
...(startOpts.signRecord
|
|
182
|
+
? {
|
|
183
|
+
signRecord: startOpts.signRecord,
|
|
184
|
+
onSignError: (err: unknown) =>
|
|
185
|
+
process.stderr.write(`warning: run "${runId}" of "${op.name}" was not signed: ${err instanceof Error ? err.message : String(err)}\n`),
|
|
186
|
+
}
|
|
187
|
+
: {}),
|
|
179
188
|
// #3956: a running step's output, and a still-running line while
|
|
180
189
|
// it is quiet, on stderr as they happen; stdout stays the
|
|
181
190
|
// `--json` record's and `--progress-json`'s.
|
package/src/plan-summary.test.ts
CHANGED
|
@@ -261,3 +261,23 @@ describe("addresses", () => {
|
|
|
261
261
|
expect(escapeMarkerAddress('aws_s3_bucket.logs["a*b"]')).toBe("aws_s3_bucket.logs:a+00002Ab");
|
|
262
262
|
});
|
|
263
263
|
});
|
|
264
|
+
|
|
265
|
+
describe("a no-change group with holes (#4084)", () => {
|
|
266
|
+
const part = (member: string, holes: ChangeSetPart["member"]["holes"] = []): ChangeSetPart => ({
|
|
267
|
+
member: { member, lexicon: "aws", planner: "chant", status: "planned", planDigest: `jcs1-sha256:${member}`, holes },
|
|
268
|
+
entries: [],
|
|
269
|
+
});
|
|
270
|
+
const hole = { address: "cluster-a", type: "AWS::CloudFormation::Stack", reason: "inputs from network; re-checked on apply" };
|
|
271
|
+
|
|
272
|
+
test("does not say no changes for a member whose plan has holes, and keeps the group and its id", () => {
|
|
273
|
+
const withHole = groupChangeSet(composeChangeSet([part("app-01"), part("app-02"), part("cluster-a", [hole])]));
|
|
274
|
+
const without = groupChangeSet(composeChangeSet([part("app-01"), part("app-02"), part("cluster-a")]));
|
|
275
|
+
expect(withHole.groups.map((g) => [g.id, g.units, g.noChanges])).toEqual(without.groups.map((g) => [g.id, g.units, g.noChanges]));
|
|
276
|
+
const id = withHole.groups[0].id;
|
|
277
|
+
const text = renderPlanSummaryText(withHole);
|
|
278
|
+
expect(text).toContain(`Group ${id}: 3 members, no changes known: 1 with holes, listed above:`);
|
|
279
|
+
expect(text).toContain("cluster-a: cluster-a: inputs from network; re-checked on apply");
|
|
280
|
+
expect(renderPlanSummaryMarkdown(withHole)).toContain(`#### Group ${id}: 3 members, no changes known: 1 with holes, listed above`);
|
|
281
|
+
expect(renderPlanSummaryText(without)).toContain(`Group ${id}: 3 members, no changes:`);
|
|
282
|
+
});
|
|
283
|
+
});
|
package/src/plan-summary.ts
CHANGED
|
@@ -689,7 +689,14 @@ function groupTitle(s: PlanSummary, g: PlanSummaryGroup): string {
|
|
|
689
689
|
if (g.resource) t += ` of ${g.resource}`;
|
|
690
690
|
if (g.outlier) t += " (outlier)";
|
|
691
691
|
if (g.provisional) t += " (provisional)";
|
|
692
|
-
if (g.noChanges)
|
|
692
|
+
if (g.noChanges) {
|
|
693
|
+
// A member with holes planned no entries, but its plan says nothing about
|
|
694
|
+
// what the holes cover, so the group cannot say "no changes" for it
|
|
695
|
+
// (#4084). Only the heading says so: the group and its id are unchanged.
|
|
696
|
+
const holed = s.unit === "member" ? new Set(s.holes.map((h) => h.member)) : new Set<string>();
|
|
697
|
+
const unknown = g.units.filter((u) => holed.has(u)).length;
|
|
698
|
+
t += unknown > 0 ? `, no changes known: ${unknown} with holes, listed above` : ", no changes";
|
|
699
|
+
}
|
|
693
700
|
else if (g.extends) t += `, group ${g.extends}'s change plus`;
|
|
694
701
|
else if (g.units.length > 1) t += ", identical change";
|
|
695
702
|
else t += ", change";
|
package/src/point-decision.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { parseDuration } from "./op/duration";
|
|
|
36
36
|
import type { GateLedgerPort } from "./op/gate";
|
|
37
37
|
import { DEFAULT_GATE_POINT_PASS, defaultGatePointStore, gatePointInputs, gateSubject, type GatePoint, type GatePointFacts, type GatePointStore } from "./op/gate-point";
|
|
38
38
|
import type { WaitingPoint } from "./op/steward-points";
|
|
39
|
-
import { resolveGateRunLocator, type GateRunLocator } from "./op/gate-run-locator";
|
|
39
|
+
import { ciCommitFromEnv, ciRunIdFromEnv, resolveGateRunLocator, type GateRunLocator } from "./op/gate-run-locator";
|
|
40
40
|
import { refuseGateWithoutRecording } from "./lifecycle/no-record";
|
|
41
41
|
|
|
42
42
|
export {
|
|
@@ -102,6 +102,13 @@ export interface GatePointAsker {
|
|
|
102
102
|
ask(request: GatePointRequest): Promise<GatePointQuestion>;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/** The run and commit a gate record names (#4020): the input's own, else the CI job's. */
|
|
106
|
+
function runStamp(input: { runId?: string; gitSha?: string }): { runId?: string; gitSha?: string } {
|
|
107
|
+
const runId = input.runId || ciRunIdFromEnv();
|
|
108
|
+
const gitSha = input.gitSha || ciCommitFromEnv();
|
|
109
|
+
return { ...(runId ? { runId } : {}), ...(gitSha ? { gitSha } : {}) };
|
|
110
|
+
}
|
|
111
|
+
|
|
105
112
|
/** What the run needs to decide a gate that asks a point. */
|
|
106
113
|
export interface PointGateInput {
|
|
107
114
|
op: string;
|
|
@@ -112,6 +119,8 @@ export interface PointGateInput {
|
|
|
112
119
|
description?: string;
|
|
113
120
|
timeout?: string;
|
|
114
121
|
runId?: string;
|
|
122
|
+
/** The commit the run is at (#4020). Default: the commit the CI job started for, when it runs in CI. */
|
|
123
|
+
gitSha?: string;
|
|
115
124
|
planDigest?: string;
|
|
116
125
|
/** The environment the run was started for: the `gate.env` input. Op gate facts on the ledger record none. */
|
|
117
126
|
env?: string;
|
|
@@ -202,6 +211,7 @@ export async function evaluatePointGate(port: PointGateLedgerPort, asker: GatePo
|
|
|
202
211
|
resolvedBy: resolvedByOf(q),
|
|
203
212
|
timestamp: now,
|
|
204
213
|
note: `answered through decision point ${q.point} (${q.id})`,
|
|
214
|
+
...runStamp(input),
|
|
205
215
|
...(input.planDigest !== undefined ? { planDigest: input.planDigest } : {}),
|
|
206
216
|
approver: { kind: q.decider === "quorum" || q.answeredBy.length > 0 ? "human" : "agent" },
|
|
207
217
|
answer,
|
|
@@ -228,7 +238,7 @@ export async function evaluatePointGate(port: PointGateLedgerPort, asker: GatePo
|
|
|
228
238
|
timestamp: now,
|
|
229
239
|
expiresAt: new Date(new Date(now).getTime() + parseDuration(input.timeout ?? DEFAULT_GATE_EXPIRY)).toISOString(),
|
|
230
240
|
...(input.description ? { description: input.description } : {}),
|
|
231
|
-
...(input
|
|
241
|
+
...runStamp(input),
|
|
232
242
|
...(url ? { url } : {}),
|
|
233
243
|
...(input.planDigest !== undefined ? { planDigest: input.planDigest } : {}),
|
|
234
244
|
...(resume ? { resume } : {}),
|
package/src/pr-loop.ts
CHANGED
|
@@ -204,6 +204,17 @@ export function memberCounts(doc: Pick<ChangeSetDocument, "summary">, member: st
|
|
|
204
204
|
return { create: n("create"), update: n("update"), replace: n("replace"), delete: n("delete") };
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Why a member's plan cannot say everything it will change: its holes'
|
|
209
|
+
* reasons, each once, in order (#4084). A `cfn-deploy` member whose inputs
|
|
210
|
+
* come from a stack the same run applies first says `inputs from <stack>;
|
|
211
|
+
* re-checked on apply`.
|
|
212
|
+
*/
|
|
213
|
+
export function memberHoleReasons(report: Pick<PrReport, "changeSet">, member: string): string[] {
|
|
214
|
+
const m = report.changeSet.members.find((x) => x.member === member);
|
|
215
|
+
return [...new Set((m?.holes ?? []).map((h) => h.reason))];
|
|
216
|
+
}
|
|
217
|
+
|
|
207
218
|
/** The approve command a reviewer runs for this report's digest. */
|
|
208
219
|
export function prApproveCommand(report: Pick<PrReport, "op" | "gate" | "digest">, approver = "<you>"): string {
|
|
209
220
|
return `chant approve ${report.op} ${report.gate} --plan ${report.digest} --approver ${approver} --sign`;
|
|
@@ -295,8 +306,16 @@ export function renderPrNote(report: PrReport, options: { limit?: number; approv
|
|
|
295
306
|
if (report.members.length > 0) {
|
|
296
307
|
out.push("", "| Member | Component | Status | Create | Update | Replace | Delete |", "|---|---|---|---:|---:|---:|---:|");
|
|
297
308
|
for (const m of report.members) {
|
|
298
|
-
const
|
|
299
|
-
|
|
309
|
+
const holes = memberHoleReasons(report, m.member);
|
|
310
|
+
const status =
|
|
311
|
+
STATUS_WORDS[m.status] +
|
|
312
|
+
(m.inputsMoved?.length ? `, inputs from ${m.inputsMoved.join(", ")} moved` : "") +
|
|
313
|
+
(holes.length > 0 ? `; ${holes.length > 2 ? `${holes[0]}; and ${holes.length - 1} more, listed under Holes` : holes.join("; ")}` : "");
|
|
314
|
+
// A member whose plan has holes and no entries says nothing about what
|
|
315
|
+
// it changes, so its counts are unknown, not zero (#4084).
|
|
316
|
+
const unknown = holes.length > 0 && !report.changeSet.entries.some((e) => e.member === m.member);
|
|
317
|
+
const n = (k: keyof PrMemberCounts): string => (unknown ? "?" : String(m.counts[k]));
|
|
318
|
+
out.push(`| ${code(m.member)} | ${m.component} | ${status} | ${n("create")} | ${n("update")} | ${n("replace")} | ${n("delete")} |`);
|
|
300
319
|
}
|
|
301
320
|
if (report.members.some((m) => m.inputsMoved?.length)) {
|
|
302
321
|
out.push("", "A member whose inputs moved applied the plan it was approved with. The next run plans it against the new values.");
|
|
@@ -328,9 +347,11 @@ export function prStatusDescription(report: PrReport): string {
|
|
|
328
347
|
case "nothing":
|
|
329
348
|
text = "nothing to plan";
|
|
330
349
|
break;
|
|
331
|
-
case "planned":
|
|
332
|
-
|
|
350
|
+
case "planned": {
|
|
351
|
+
const unknown = report.members.filter((m) => memberHoleReasons(report, m.member).length > 0).length;
|
|
352
|
+
text = `${n} member${n === 1 ? "" : "s"}: ${sum.change} to change, ${sum.destroy} to destroy or replace${unknown > 0 ? `, ${unknown} with holes` : ""}${digest}`;
|
|
333
353
|
break;
|
|
354
|
+
}
|
|
334
355
|
case "plan-failed":
|
|
335
356
|
text = `a member failed to plan${digest}`;
|
|
336
357
|
break;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--report-url` (#4007): the URL forms, the key, and the upload of a run's
|
|
3
|
+
* events as it goes: one upload in flight at a time, a failure that warns
|
|
4
|
+
* and never stops the run, and a last upload that carries everything. The
|
|
5
|
+
* s3 scheme runs a fake AWS CLI; nothing here reaches a network.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { pathToFileURL } from "node:url";
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
13
|
+
import { openPrReportUpload, parseReportUrl, reportKey, reportPut, type ReportPut } from "./pr-report-url";
|
|
14
|
+
|
|
15
|
+
const line = (type: string, extra: Record<string, unknown> = {}): string => JSON.stringify({ type, ...extra }) + "\n";
|
|
16
|
+
|
|
17
|
+
let dir: string;
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
dir = await mkdtemp(join(tmpdir(), "pr-report-url-"));
|
|
20
|
+
});
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
await rm(dir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("parseReportUrl", () => {
|
|
26
|
+
test("reads s3 and file URLs and refuses anything else", () => {
|
|
27
|
+
expect(parseReportUrl("s3://bucket/a/b/")).toEqual({ scheme: "s3", url: "s3://bucket/a/b", bucket: "bucket", prefix: "a/b" });
|
|
28
|
+
expect(parseReportUrl("s3://bucket")).toEqual({ scheme: "s3", url: "s3://bucket", bucket: "bucket", prefix: "" });
|
|
29
|
+
expect(parseReportUrl(pathToFileURL(dir).href)).toMatchObject({ scheme: "file", dir });
|
|
30
|
+
expect(parseReportUrl("https://example.invalid/x")).toHaveProperty("error");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("keys by pull request and commit, and by member after them", () => {
|
|
34
|
+
expect(reportKey(12, "abc")).toBe("12/abc");
|
|
35
|
+
expect(reportKey(null, "abc")).toBe("unknown/abc");
|
|
36
|
+
expect(reportKey(12, "abc", "api")).toBe("12/abc/api");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("openPrReportUpload", () => {
|
|
41
|
+
/** A put whose uploads finish only when the test says, recording what each carried. */
|
|
42
|
+
function heldPut() {
|
|
43
|
+
const calls: Array<{ key: string; body: string; finish: (err?: Error) => void }> = [];
|
|
44
|
+
let inFlight = 0;
|
|
45
|
+
let most = 0;
|
|
46
|
+
const put: ReportPut = (key, body) =>
|
|
47
|
+
new Promise<void>((resolve, reject) => {
|
|
48
|
+
inFlight++;
|
|
49
|
+
most = Math.max(most, inFlight);
|
|
50
|
+
calls.push({ key, body, finish: (err) => { inFlight--; if (err) reject(err); else resolve(); } });
|
|
51
|
+
});
|
|
52
|
+
return { put, calls, most: () => most };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
test("uploads the events on each settle, never two at once, and the settles that came meanwhile in one more", async () => {
|
|
56
|
+
const held = heldPut();
|
|
57
|
+
const upload = openPrReportUpload({ target: { scheme: "s3", url: "s3://b", bucket: "b", prefix: "" }, key: "12/abc", stage: "apply", warn: () => {}, put: held.put });
|
|
58
|
+
upload.line(line("run-start"));
|
|
59
|
+
expect(held.calls).toHaveLength(0);
|
|
60
|
+
upload.line(line("wave-start", { wave: 1 }));
|
|
61
|
+
upload.line(line("component-settle", { wave: 1 }));
|
|
62
|
+
expect(held.calls).toHaveLength(1);
|
|
63
|
+
upload.line(line("wave-settle", { wave: 1 }));
|
|
64
|
+
upload.line(line("wave-start", { wave: 2 }));
|
|
65
|
+
upload.line(line("component-settle", { wave: 2 }));
|
|
66
|
+
// Both settles wait behind the one in flight, and go up as one.
|
|
67
|
+
expect(held.calls).toHaveLength(1);
|
|
68
|
+
held.calls[0]!.finish();
|
|
69
|
+
await new Promise((r) => setImmediate(r));
|
|
70
|
+
expect(held.calls).toHaveLength(2);
|
|
71
|
+
expect(held.calls[1]!.body.split("\n").filter(Boolean)).toHaveLength(6);
|
|
72
|
+
expect(held.calls[1]!.key).toBe("12/abc/pr-apply.events.jsonl");
|
|
73
|
+
|
|
74
|
+
upload.file("pr-apply.json", "{}\n");
|
|
75
|
+
const closing = upload.close();
|
|
76
|
+
await new Promise((r) => setImmediate(r));
|
|
77
|
+
// The last upload waits for the one in flight.
|
|
78
|
+
expect(held.calls).toHaveLength(2);
|
|
79
|
+
held.calls[1]!.finish();
|
|
80
|
+
await new Promise((r) => setImmediate(r));
|
|
81
|
+
held.calls[2]!.finish();
|
|
82
|
+
await new Promise((r) => setImmediate(r));
|
|
83
|
+
held.calls[3]!.finish();
|
|
84
|
+
await closing;
|
|
85
|
+
expect(held.calls.map((c) => c.key)).toEqual(["12/abc/pr-apply.events.jsonl", "12/abc/pr-apply.events.jsonl", "12/abc/pr-apply.events.jsonl", "12/abc/pr-apply.json"]);
|
|
86
|
+
expect(held.most()).toBe(1);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("a failed upload as the run goes warns once and the run goes on; the last one is tried again", async () => {
|
|
90
|
+
const warnings: string[] = [];
|
|
91
|
+
let fail = true;
|
|
92
|
+
const put: ReportPut = async (key) => {
|
|
93
|
+
if (fail) throw new Error(`no bucket for ${key}`);
|
|
94
|
+
};
|
|
95
|
+
const upload = openPrReportUpload({ target: { scheme: "s3", url: "s3://b", bucket: "b", prefix: "" }, key: "12/abc", stage: "apply", warn: (m) => warnings.push(m), put });
|
|
96
|
+
upload.line(line("component-settle"));
|
|
97
|
+
await new Promise((r) => setImmediate(r));
|
|
98
|
+
upload.line(line("wave-settle"));
|
|
99
|
+
await new Promise((r) => setImmediate(r));
|
|
100
|
+
expect(warnings).toHaveLength(1);
|
|
101
|
+
expect(warnings[0]).toMatch(/as the run went: no bucket .*The run goes on/);
|
|
102
|
+
fail = false;
|
|
103
|
+
await upload.close();
|
|
104
|
+
expect(warnings).toHaveLength(1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("file://: each settle replaces the events file whole, so it holds a wave's settle before the next wave starts", async () => {
|
|
108
|
+
const target = parseReportUrl(pathToFileURL(dir).href);
|
|
109
|
+
if ("error" in target) throw new Error(target.error);
|
|
110
|
+
const upload = openPrReportUpload({ target, key: "12/abc", stage: "apply", warn: () => {} });
|
|
111
|
+
upload.line(line("run-start"));
|
|
112
|
+
upload.line(line("wave-settle", { wave: 1 }));
|
|
113
|
+
await new Promise((r) => setImmediate(r));
|
|
114
|
+
const events = join(dir, "12", "abc", "pr-apply.events.jsonl");
|
|
115
|
+
expect(await readFile(events, "utf8")).toBe(line("run-start") + line("wave-settle", { wave: 1 }));
|
|
116
|
+
upload.line(line("run-settle"));
|
|
117
|
+
upload.file("change-set.json", "{}\n");
|
|
118
|
+
await upload.close();
|
|
119
|
+
expect((await readFile(events, "utf8")).split("\n").filter(Boolean)).toHaveLength(3);
|
|
120
|
+
expect(await readFile(join(dir, "12", "abc", "change-set.json"), "utf8")).toBe("{}\n");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("s3://: the AWS CLI copies each object from its stdin, with the content type, and chant hands it no credential", async () => {
|
|
124
|
+
const runs: Array<{ args: string[]; stdin: string }> = [];
|
|
125
|
+
const put = reportPut({ scheme: "s3", url: "s3://b/p", bucket: "b", prefix: "p" }, async (args, stdin) => {
|
|
126
|
+
runs.push({ args, stdin });
|
|
127
|
+
});
|
|
128
|
+
await put("12/abc/pr-plan.json", "{}\n", "application/json");
|
|
129
|
+
expect(runs).toEqual([{ args: ["s3", "cp", "-", "s3://b/p/12/abc/pr-plan.json", "--content-type", "application/json", "--only-show-errors"], stdin: "{}\n" }]);
|
|
130
|
+
});
|
|
131
|
+
});
|