@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,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store the change set a gate binds (#4022), mapped onto the member's graph.
|
|
3
|
+
*
|
|
4
|
+
* {@link storeGateChangeSet} is what a gate that binds a change-set document's
|
|
5
|
+
* digest calls before it decides the gate: `components pr-apply`, and
|
|
6
|
+
* `components promote --release` before it decides a release gate or writes
|
|
7
|
+
* an `empty` resolution (#4045). It reads the member's
|
|
8
|
+
* graph the way `chant workspace graph` reads it, from the member's own
|
|
9
|
+
* `chant graph --format ir`, maps each entry onto a node
|
|
10
|
+
* (`../change-set-graph.ts`), and stores the document under its digest
|
|
11
|
+
* (`./change-set-store.ts`).
|
|
12
|
+
*
|
|
13
|
+
* Each entry mapped onto a node that has a source location also records that
|
|
14
|
+
* file from the repository root as `source` (#4025), which the overlap report
|
|
15
|
+
* in `workspace status` attributes the entry to work items by.
|
|
16
|
+
*
|
|
17
|
+
* The graph is read only when the digest is not stored yet. A graph that
|
|
18
|
+
* can't be read never stops the gate: the document is stored with `graph:
|
|
19
|
+
* null` and every `node` null, and the caller gets a warning to print.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
23
|
+
import { posix } from "node:path";
|
|
24
|
+
import { mapChangeSetToGraph, type ChangeSetGraphNodes } from "../change-set-graph";
|
|
25
|
+
import type { ChangeSetDocument, ChangeSetGraph } from "../change-set";
|
|
26
|
+
import { persistChangeSet, readStoredChangeSet } from "./change-set-store";
|
|
27
|
+
import { resolveMemberLedger } from "./member-ledger";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reads the node ids of the graph of the project at `projectDir`, with each
|
|
31
|
+
* node's source file (relative to `projectDir`) when the graph gives one.
|
|
32
|
+
*/
|
|
33
|
+
export type GraphIdReader = (projectDir: string) => Promise<{ ids: string[]; sources?: Record<string, string> } | { error: string }>;
|
|
34
|
+
|
|
35
|
+
const GRAPH_TIMEOUT_MS = 300_000;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The node ids of `chant graph --format ir` in `projectDir`, run under the
|
|
39
|
+
* chant that directory resolves (its own `node_modules/.bin/chant`, else this
|
|
40
|
+
* one), as `chant workspace graph` runs each member. `args` are passed after
|
|
41
|
+
* `graph --format ir`, such as the run's `--param` flags.
|
|
42
|
+
*/
|
|
43
|
+
export async function readGraphIds(projectDir: string, args: readonly string[] = []): Promise<{ ids: string[]; sources: Record<string, string> } | { error: string }> {
|
|
44
|
+
const { readerToolchain, resolveToolchain } = await import("../workspace/member-commands");
|
|
45
|
+
const { readMemberIr } = await import("../workspace/compose-graph");
|
|
46
|
+
const { findWorkspaceRoot } = await import("../project-root");
|
|
47
|
+
const root = findWorkspaceRoot(projectDir)?.dir ?? projectDir;
|
|
48
|
+
const toolchain = resolveToolchain(projectDir, root, readerToolchain());
|
|
49
|
+
const [command, ...pre] = toolchain.command;
|
|
50
|
+
const out = await new Promise<{ code: number | null; stdout: string; stderr: string }>((resolveRun) => {
|
|
51
|
+
const child = spawn(command, [...pre, "graph", "--format", "ir", ...args], { cwd: projectDir, stdio: ["ignore", "pipe", "pipe"] });
|
|
52
|
+
let stdout = "";
|
|
53
|
+
let stderr = "";
|
|
54
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), GRAPH_TIMEOUT_MS);
|
|
55
|
+
child.stdout.on("data", (d) => (stdout += d));
|
|
56
|
+
child.stderr.on("data", (d) => (stderr += d));
|
|
57
|
+
child.on("error", (err) => {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
resolveRun({ code: null, stdout, stderr: err.message });
|
|
60
|
+
});
|
|
61
|
+
child.on("close", (code) => {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
resolveRun({ code, stdout, stderr });
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
if (out.code !== 0) {
|
|
67
|
+
const line = out.stderr.split("\n").map((l) => l.trim()).find(Boolean);
|
|
68
|
+
return { error: `chant graph --format ir exited ${out.code ?? "on a signal"}${line ? `: ${line}` : ""}` };
|
|
69
|
+
}
|
|
70
|
+
const read = readMemberIr(out.stdout);
|
|
71
|
+
if ("reason" in read) return { error: read.reason.message };
|
|
72
|
+
const sources: Record<string, string> = {};
|
|
73
|
+
for (const n of read.ir.nodes) if (n.sourceLoc?.file) sources[n.id] = n.sourceLoc.file;
|
|
74
|
+
return { ids: read.ir.nodes.map((n) => n.id), sources };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let graphReaderOverride: GraphIdReader | undefined;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Replace how a gate reads the graph wherever the default is taken, such as
|
|
81
|
+
* in `components pr-apply`; `undefined` restores {@link readGraphIds}. For
|
|
82
|
+
* tests that drive a gate and read node ids in process, so they don't start a
|
|
83
|
+
* `chant graph` child process (`gate-change-set.e2e.test.ts` covers that one).
|
|
84
|
+
*/
|
|
85
|
+
export function setGraphIdReader(reader: GraphIdReader | undefined): void {
|
|
86
|
+
graphReaderOverride = reader;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The graph reader a gate uses: the one {@link setGraphIdReader} set, else {@link readGraphIds} with `args`. */
|
|
90
|
+
export function gateGraphReader(args: readonly string[] = []): GraphIdReader {
|
|
91
|
+
return graphReaderOverride ?? ((dir) => readGraphIds(dir, args));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface StoreGateChangeSetInput {
|
|
95
|
+
/** The document whose `digest` the gate binds. */
|
|
96
|
+
doc: ChangeSetDocument;
|
|
97
|
+
/** The member's directory: where the graph is read and whose ledger it is stored in. */
|
|
98
|
+
cwd: string;
|
|
99
|
+
/** The commit the document was planned at, recorded as the graph's `at`. */
|
|
100
|
+
at?: string | null;
|
|
101
|
+
/** How to read the graph. Defaults to {@link gateGraphReader}. */
|
|
102
|
+
readGraph?: GraphIdReader;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface StoreGateChangeSetResult {
|
|
106
|
+
digest: string;
|
|
107
|
+
/** True when this call wrote the document; false when it was stored already. */
|
|
108
|
+
written: boolean;
|
|
109
|
+
/** The graph the stored document's nodes come from, null when none could be read. Absent when it was stored already. */
|
|
110
|
+
graph?: ChangeSetGraph | null;
|
|
111
|
+
/** Why no graph was read, when none was. */
|
|
112
|
+
warning?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Each node's source file as a path from the repository root (#4025): the
|
|
117
|
+
* graph gives it relative to the member's directory at `cwd`. Undefined when
|
|
118
|
+
* the graph gives none or `cwd` is in no git repository.
|
|
119
|
+
*/
|
|
120
|
+
function repoSources(cwd: string, sources: Record<string, string> | undefined): Map<string, string> | undefined {
|
|
121
|
+
if (!sources || Object.keys(sources).length === 0) return undefined;
|
|
122
|
+
let prefix: string;
|
|
123
|
+
try {
|
|
124
|
+
prefix = execFileSync("git", ["rev-parse", "--show-prefix"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
125
|
+
} catch {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
const out = new Map<string, string>();
|
|
129
|
+
for (const [id, file] of Object.entries(sources)) {
|
|
130
|
+
const path = posix.normalize(posix.join(prefix, file.split("\\").join("/")));
|
|
131
|
+
if (!path.startsWith("../") && path !== "..") out.set(id, path);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Store `doc` under its digest in the member's ledger at `cwd`, mapped onto
|
|
138
|
+
* the member's graph, unless it is stored already. Does not push; the gate's
|
|
139
|
+
* pending fact or resolution pushes the branch after it.
|
|
140
|
+
*/
|
|
141
|
+
export async function storeGateChangeSet(input: StoreGateChangeSetInput): Promise<StoreGateChangeSetResult> {
|
|
142
|
+
const { doc, cwd } = input;
|
|
143
|
+
const loc = { cwd };
|
|
144
|
+
if ((await readStoredChangeSet(doc.digest, loc)).status !== "missing") return { digest: doc.digest, written: false };
|
|
145
|
+
const ledger = await resolveMemberLedger(cwd);
|
|
146
|
+
const member = ledger.members.length > 0 ? ledger.members.join("/") : null;
|
|
147
|
+
let nodes: ChangeSetGraphNodes | null = null;
|
|
148
|
+
let warning: string | undefined;
|
|
149
|
+
try {
|
|
150
|
+
const read = await (input.readGraph ?? gateGraphReader())(cwd);
|
|
151
|
+
if ("error" in read) warning = read.error;
|
|
152
|
+
else nodes = { member, at: input.at ?? null, ids: read.ids, sources: repoSources(cwd, read.sources) };
|
|
153
|
+
} catch (err) {
|
|
154
|
+
warning = err instanceof Error ? err.message : String(err);
|
|
155
|
+
}
|
|
156
|
+
const mapped = mapChangeSetToGraph(doc, nodes);
|
|
157
|
+
const { written } = await persistChangeSet(mapped, loc);
|
|
158
|
+
return {
|
|
159
|
+
digest: doc.digest,
|
|
160
|
+
written,
|
|
161
|
+
graph: mapped.graph ?? null,
|
|
162
|
+
...(warning ? { warning: `the change set is stored without graph nodes: ${warning}` } : {}),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -503,3 +503,36 @@ describe("pending fact wave and heldBy (#3886, #3887)", () => {
|
|
|
503
503
|
expect(pending[0]!.heldBy).toBeUndefined();
|
|
504
504
|
});
|
|
505
505
|
});
|
|
506
|
+
|
|
507
|
+
describe("commit and run on gate records (#4020)", () => {
|
|
508
|
+
const base = { version: 1, op: "tg", gate: "g", timestamp: "2026-10-01T00:00:00Z" };
|
|
509
|
+
const parse = (...lines: Record<string, unknown>[]) => parseGateLedger(lines.map((l) => JSON.stringify(l)).join("\n"));
|
|
510
|
+
test("a pending fact and a resolution read back with their commit and run", () => {
|
|
511
|
+
const { pending, resolutions, malformed } = parse(
|
|
512
|
+
{ ...base, kind: "pending", expiresAt: "2026-10-02T00:00:00Z", gitSha: "a".repeat(40), runId: "9001" },
|
|
513
|
+
{ ...base, resolvedBy: "alice", gitSha: "a".repeat(40), runId: "9001" },
|
|
514
|
+
);
|
|
515
|
+
expect(malformed).toBe(0);
|
|
516
|
+
expect(pending[0]).toMatchObject({ gitSha: "a".repeat(40), runId: "9001" });
|
|
517
|
+
expect(resolutions[0]).toMatchObject({ gitSha: "a".repeat(40), runId: "9001" });
|
|
518
|
+
});
|
|
519
|
+
test("lines written before the fields existed still read, with neither", () => {
|
|
520
|
+
const { pending, resolutions, malformed } = parse(
|
|
521
|
+
{ ...base, kind: "pending", expiresAt: "2026-10-02T00:00:00Z" },
|
|
522
|
+
{ ...base, resolvedBy: "alice" },
|
|
523
|
+
);
|
|
524
|
+
expect(malformed).toBe(0);
|
|
525
|
+
expect(pending[0]).not.toHaveProperty("gitSha");
|
|
526
|
+
expect(resolutions[0]).not.toHaveProperty("runId");
|
|
527
|
+
});
|
|
528
|
+
test("a commit or run of the wrong type is dropped and the line is kept", () => {
|
|
529
|
+
const { pending, resolutions, malformed } = parse(
|
|
530
|
+
{ ...base, kind: "pending", expiresAt: "2026-10-02T00:00:00Z", gitSha: 7, runId: { id: 1 } },
|
|
531
|
+
{ ...base, resolvedBy: "alice", gitSha: ["x"] },
|
|
532
|
+
);
|
|
533
|
+
expect(malformed).toBe(0);
|
|
534
|
+
expect(pending[0]!.gitSha).toBeUndefined();
|
|
535
|
+
expect(pending[0]!.runId).toBeUndefined();
|
|
536
|
+
expect(resolutions[0]!.gitSha).toBeUndefined();
|
|
537
|
+
});
|
|
538
|
+
});
|
|
@@ -47,7 +47,7 @@ import { currentGateOrigin, type GateOrigin } from "./gate-origin";
|
|
|
47
47
|
import type { GateApprover, GatePolicyDecision, ResolvedGateApproval } from "../op/gate-approval";
|
|
48
48
|
import type { GateApprovalSource } from "../op/op-waves";
|
|
49
49
|
import type { GateRunLocator } from "../op/gate-run-locator";
|
|
50
|
-
import { readBlobFromPath, readPathSha, readBlobBySha, writeBlobToPath, RefCASConflictError } from "./git";
|
|
50
|
+
import { readBlobFromPath, readPathSha, readBlobBySha, writeBlobToPath, RefCASConflictError, type LedgerLocation } from "./git";
|
|
51
51
|
|
|
52
52
|
// The facts a pending record is written with live apart, so a bundle can import them without git (#3816).
|
|
53
53
|
export { DEFAULT_GATE_EXPIRY, isApprovalUrl, resolveApprovalUrl } from "./gate-pending";
|
|
@@ -138,6 +138,16 @@ export interface GateResolutionRecord {
|
|
|
138
138
|
* such a record proves is that somebody approved *something*.
|
|
139
139
|
*/
|
|
140
140
|
planDigest?: string;
|
|
141
|
+
/**
|
|
142
|
+
* The commit the gate was waiting on (#4020), copied off the pending fact the
|
|
143
|
+
* approval answers, or the run's own commit for a resolution a run writes
|
|
144
|
+
* itself (a decision point's answer, a review, a carried approval). It is not
|
|
145
|
+
* the approver's checkout, and a seal does not cover it. Absent on a
|
|
146
|
+
* resolution written before 0.124.28 and when no commit was known.
|
|
147
|
+
*/
|
|
148
|
+
gitSha?: string;
|
|
149
|
+
/** The run the gate was waiting on (#4020), copied the same way as {@link GateResolutionRecord.gitSha}. */
|
|
150
|
+
runId?: string;
|
|
141
151
|
/**
|
|
142
152
|
* The checkpoint this approval lets a run resume from (#3947), copied off
|
|
143
153
|
* the pending fact it answers. Set only on an approval of a gate that
|
|
@@ -151,6 +161,26 @@ export interface GateResolutionRecord {
|
|
|
151
161
|
* recorded for its own environment and plan.
|
|
152
162
|
*/
|
|
153
163
|
environment?: string;
|
|
164
|
+
/**
|
|
165
|
+
* The release this approval is for (#4045, ws-104 question 2), copied off
|
|
166
|
+
* the pending fact it answers. Set on a release gate, the gate `chant
|
|
167
|
+
* components promote --release` passes for an environment. A release gate
|
|
168
|
+
* counts only a resolution recorded for its own release, environment and
|
|
169
|
+
* change-set document digest (`planDigest`), so it names the release it
|
|
170
|
+
* admitted. Absent on every other gate, and on a resolution written before
|
|
171
|
+
* 0.124.28.
|
|
172
|
+
*/
|
|
173
|
+
releaseDigest?: string;
|
|
174
|
+
/**
|
|
175
|
+
* The resolution passes a release gate over an empty change set (#4045,
|
|
176
|
+
* ws-104 question 6): the promote wrote it itself, with the actor running
|
|
177
|
+
* it in `resolvedBy`, because the change set its `planDigest` names changes
|
|
178
|
+
* nothing. A gate counts it only after it reads the stored document under
|
|
179
|
+
* `planDigest` and finds it empty, and neither a quorum nor a signing rule
|
|
180
|
+
* applies to it. It never counts as a person's approval. Absent on every
|
|
181
|
+
* other resolution.
|
|
182
|
+
*/
|
|
183
|
+
empty?: true;
|
|
154
184
|
/**
|
|
155
185
|
* The channel this resolution was authored on (chant#2384) — set by the
|
|
156
186
|
* writer, never by the caller. `chant approve` records `"cli"`, the
|
|
@@ -292,8 +322,19 @@ export interface PendingGateRecord {
|
|
|
292
322
|
gate: string;
|
|
293
323
|
/** The gate's human-readable description, when it declared one — what `chant operator status` shows a reader who wasn't there for the run. */
|
|
294
324
|
description?: string;
|
|
295
|
-
/**
|
|
325
|
+
/**
|
|
326
|
+
* The run that reached the gate. An Op run records its own id; a run with no
|
|
327
|
+
* id of its own (a component gate) records the CI run id when it ran in CI
|
|
328
|
+
* (#4020). Absent outside CI for a run that mints none.
|
|
329
|
+
*/
|
|
296
330
|
runId?: string;
|
|
331
|
+
/**
|
|
332
|
+
* The commit the run reached the gate at (#4020): the checkout's `HEAD`, else
|
|
333
|
+
* the commit the CI job started for. It is the commit the gate waits on, and
|
|
334
|
+
* `chant approve` copies it onto the resolution. Absent on a record written
|
|
335
|
+
* before 0.124.28 and when the run knew no commit.
|
|
336
|
+
*/
|
|
337
|
+
gitSha?: string;
|
|
297
338
|
/** ISO-8601 timestamp, caller-supplied (library code never calls `Date.now()` internally). */
|
|
298
339
|
timestamp: string;
|
|
299
340
|
/** ISO-8601 expiry, from the gate's `timeout` (default {@link DEFAULT_GATE_EXPIRY}). Past it, the fact is stale and a run re-records it. */
|
|
@@ -334,6 +375,13 @@ export interface PendingGateRecord {
|
|
|
334
375
|
* can pick one.
|
|
335
376
|
*/
|
|
336
377
|
environment?: string;
|
|
378
|
+
/**
|
|
379
|
+
* The release the run reached this gate for (#4045): on a release gate, the
|
|
380
|
+
* `releaseDigest` of the release `chant components promote --release`
|
|
381
|
+
* deploys. `chant approve` copies it onto the approval with the plan.
|
|
382
|
+
* Absent on every other gate, and on a fact written before 0.124.28.
|
|
383
|
+
*/
|
|
384
|
+
releaseDigest?: string;
|
|
337
385
|
/**
|
|
338
386
|
* The channel the run that reached this gate was driven from (chant#2384).
|
|
339
387
|
*
|
|
@@ -471,7 +519,7 @@ async function appendGateLine(
|
|
|
471
519
|
*/
|
|
472
520
|
export async function readGateLedger(
|
|
473
521
|
op: string,
|
|
474
|
-
opts?:
|
|
522
|
+
opts?: LedgerLocation,
|
|
475
523
|
): Promise<{ resolutions: GateResolutionRecord[]; pending: PendingGateRecord[]; malformed: number }> {
|
|
476
524
|
const content = await readBlobFromPath(DIR, filename(op), opts);
|
|
477
525
|
if (!content) return { resolutions: [], pending: [], malformed: 0 };
|
|
@@ -519,12 +567,29 @@ export function parseGateLedger(
|
|
|
519
567
|
malformed++;
|
|
520
568
|
continue;
|
|
521
569
|
}
|
|
570
|
+
// #4045: and for the release a release gate approval is bound to.
|
|
571
|
+
const rawRelease: unknown = (parsed as { releaseDigest?: unknown }).releaseDigest;
|
|
572
|
+
if (rawRelease !== undefined && typeof rawRelease !== "string") {
|
|
573
|
+
malformed++;
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
// #4045: an `empty` that is not a boolean can't be told from a pass or an approval.
|
|
577
|
+
const rawEmpty: unknown = (parsed as { empty?: unknown }).empty;
|
|
578
|
+
if (rawEmpty !== undefined && typeof rawEmpty !== "boolean") {
|
|
579
|
+
malformed++;
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
522
582
|
// #3947: and for the checkpoint an approval lets a run resume from.
|
|
523
583
|
const rawCheckpoint: unknown = (parsed as { checkpointDigest?: unknown }).checkpointDigest;
|
|
524
584
|
if (rawCheckpoint !== undefined && typeof rawCheckpoint !== "string") {
|
|
525
585
|
malformed++;
|
|
526
586
|
continue;
|
|
527
587
|
}
|
|
588
|
+
// #4020: a commit or run id of the wrong type is dropped, so a reader can trust the type without losing the line.
|
|
589
|
+
for (const key of ["gitSha", "runId"] as const) {
|
|
590
|
+
const v: unknown = (parsed as Record<string, unknown>)[key];
|
|
591
|
+
if (v !== undefined && typeof v !== "string") delete (parsed as Record<string, unknown>)[key];
|
|
592
|
+
}
|
|
528
593
|
if (parsed.kind === "pending") {
|
|
529
594
|
if (typeof parsed.expiresAt !== "string") {
|
|
530
595
|
malformed++;
|
package/src/lifecycle/git.ts
CHANGED
|
@@ -171,6 +171,21 @@ export interface LedgerFileWrite {
|
|
|
171
171
|
expectPriorSha?: string | null;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
/**
|
|
175
|
+
* One file for {@link writeLedgerFiles} to remove (#4036): retention's
|
|
176
|
+
* garbage collection takes a stored change set, release or template out of
|
|
177
|
+
* the tree in a new commit. The history keeps it.
|
|
178
|
+
*/
|
|
179
|
+
export interface LedgerFileRemoval {
|
|
180
|
+
/** The file's full path on the branch. No prefix is added. */
|
|
181
|
+
path: string;
|
|
182
|
+
remove: true;
|
|
183
|
+
/** The blob sha the caller saw at the path; a different sha, or no file, is a conflict. */
|
|
184
|
+
expectPriorSha?: string | null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const isRemoval = (f: LedgerFileWrite | LedgerFileRemoval): f is LedgerFileRemoval => (f as LedgerFileRemoval).remove === true;
|
|
188
|
+
|
|
174
189
|
/**
|
|
175
190
|
* Write several files to the branch in one commit (#2538). Paths are full
|
|
176
191
|
* branch paths, so one commit can write the ledgers of several members, for
|
|
@@ -185,9 +200,18 @@ export interface LedgerFileWrite {
|
|
|
185
200
|
* of an unchanged directory keeps its sha.
|
|
186
201
|
*/
|
|
187
202
|
export async function writeLedgerFiles(
|
|
188
|
-
files: LedgerFileWrite
|
|
203
|
+
files: Array<LedgerFileWrite | LedgerFileRemoval>,
|
|
189
204
|
commitMessage: string,
|
|
190
|
-
opts?: {
|
|
205
|
+
opts?: {
|
|
206
|
+
cwd?: string;
|
|
207
|
+
/**
|
|
208
|
+
* The branch tip the caller read before deciding what to write (#4036).
|
|
209
|
+
* A different tip is a conflict, thrown at once and never retried here,
|
|
210
|
+
* so a caller whose choice depends on files it does not write (garbage
|
|
211
|
+
* collection reading the gate ledgers) reads again.
|
|
212
|
+
*/
|
|
213
|
+
expectTip?: string;
|
|
214
|
+
},
|
|
191
215
|
): Promise<string> {
|
|
192
216
|
const rt = getRuntime();
|
|
193
217
|
const cwd = opts?.cwd;
|
|
@@ -208,8 +232,13 @@ export async function writeLedgerFiles(
|
|
|
208
232
|
// kubectl.kubernetes.io/last-applied-configuration annotation) and no
|
|
209
233
|
// shell-quoting dance around embedded single quotes. Content-addressed
|
|
210
234
|
// and idempotent, so this stays outside the retry loop below.
|
|
211
|
-
|
|
235
|
+
// A removal has no blob: its sha is null, and step 3 deletes the entry.
|
|
236
|
+
const blobs: { path: string; sha: string | null }[] = [];
|
|
212
237
|
for (const f of files) {
|
|
238
|
+
if (isRemoval(f)) {
|
|
239
|
+
blobs.push({ path: f.path, sha: null });
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
213
242
|
const blobResult = await rt.spawn(["git", "hash-object", "-w", "--stdin"], { cwd, stdin: f.content });
|
|
214
243
|
if (blobResult.exitCode !== 0) {
|
|
215
244
|
throw ledgerWriteError("git hash-object", f.path, blobResult.stderr);
|
|
@@ -283,6 +312,9 @@ export async function writeLedgerFiles(
|
|
|
283
312
|
// there instead can observe a newer tip than the listings reflect, which
|
|
284
313
|
// makes the CAS succeed against a tree built from a stale read.
|
|
285
314
|
const tip = await getStateBranchTip(cwd);
|
|
315
|
+
if (opts?.expectTip !== undefined && tip !== opts.expectTip) {
|
|
316
|
+
throw new RefCASConflictError(`refs/heads/${STATE_BRANCH}`, opts.expectTip, `the branch moved to ${tip ?? "(none)"} since it was read`);
|
|
317
|
+
}
|
|
286
318
|
const trees = new Map<string, Map<string, TreeLine>>();
|
|
287
319
|
const listing = async (dir: string): Promise<Map<string, TreeLine>> => {
|
|
288
320
|
let entries = trees.get(dir);
|
|
@@ -312,16 +344,22 @@ export async function writeLedgerFiles(
|
|
|
312
344
|
// rebuild below has each parent in hand.
|
|
313
345
|
for (const b of blobs) {
|
|
314
346
|
const { dir, name } = splitPath(b.path);
|
|
315
|
-
(await listing(dir)).
|
|
347
|
+
if (b.sha === null) (await listing(dir)).delete(name);
|
|
348
|
+
else (await listing(dir)).set(name, { mode: "100644", type: "blob", sha: b.sha });
|
|
316
349
|
for (let d = dir; d !== ""; d = parentDir(d)) await listing(parentDir(d));
|
|
317
350
|
}
|
|
318
351
|
|
|
319
|
-
// 4. Rebuild the touched trees, deepest first, then the root.
|
|
352
|
+
// 4. Rebuild the touched trees, deepest first, then the root. A directory
|
|
353
|
+
// a removal left empty is dropped from its parent, as git itself would.
|
|
320
354
|
const touched = [...trees.keys()].sort((a, b) => depth(b) - depth(a) || (a < b ? -1 : 1));
|
|
321
355
|
let rootTreeSha = "";
|
|
322
356
|
for (const dir of touched) {
|
|
357
|
+
if (dir !== "" && trees.get(dir)!.size === 0) {
|
|
358
|
+
trees.get(parentDir(dir))!.delete(baseName(dir));
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
323
361
|
const lines = [...trees.get(dir)!].map(([name, e]) => `${e.mode} ${e.type} ${e.sha}\t${name}`).join("\n");
|
|
324
|
-
const result = await rt.spawn(["git", "mktree"], { cwd, stdin: `${lines}\n` });
|
|
362
|
+
const result = await rt.spawn(["git", "mktree"], { cwd, stdin: lines === "" ? "" : `${lines}\n` });
|
|
325
363
|
if (result.exitCode !== 0) {
|
|
326
364
|
throw ledgerWriteError(`git mktree (${dir === "" ? "root" : dir})`, path, result.stderr);
|
|
327
365
|
}
|
|
@@ -517,15 +555,30 @@ export async function appendReleaseRecordLine(
|
|
|
517
555
|
recordJson: string,
|
|
518
556
|
opts?: { cwd?: string },
|
|
519
557
|
): Promise<string> {
|
|
520
|
-
|
|
558
|
+
return appendLedgerLine(environment, "releases.jsonl", recordJson, "Release record", opts);
|
|
559
|
+
}
|
|
521
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Append one line to `<environment>/<filename>` on the orphan branch, with
|
|
563
|
+
* the same read, append and compare-and-swap retry as
|
|
564
|
+
* {@link appendReleaseRecordLine}. The deploy outcomes of
|
|
565
|
+
* ../lifecycle/deploy-outcome.ts (#4035) are appended through here, beside
|
|
566
|
+
* the environment's `releases.jsonl`.
|
|
567
|
+
*/
|
|
568
|
+
export async function appendLedgerLine(
|
|
569
|
+
environment: string,
|
|
570
|
+
filename: string,
|
|
571
|
+
recordJson: string,
|
|
572
|
+
commitMessage: string,
|
|
573
|
+
opts?: { cwd?: string },
|
|
574
|
+
): Promise<string> {
|
|
522
575
|
let lastErr: unknown;
|
|
523
576
|
for (let attempt = 1; attempt <= WRITE_BLOB_RETRY_ATTEMPTS; attempt++) {
|
|
524
577
|
try {
|
|
525
578
|
const priorSha = await readPathSha(environment, filename, opts);
|
|
526
579
|
const existing = priorSha ? await readBlobBySha(priorSha, opts) : null;
|
|
527
580
|
const content = `${existing ? `${existing.replace(/\n$/, "")}\n` : ""}${recordJson}\n`;
|
|
528
|
-
return await writeBlobToPath(environment, filename, content,
|
|
581
|
+
return await writeBlobToPath(environment, filename, content, commitMessage, {
|
|
529
582
|
...opts,
|
|
530
583
|
expectPriorPathSha: priorSha,
|
|
531
584
|
});
|
package/src/lifecycle/index.ts
CHANGED
|
@@ -12,6 +12,10 @@ export * from "./unobserved-gate";
|
|
|
12
12
|
export * from "./receipt-plan";
|
|
13
13
|
export * from "./affected";
|
|
14
14
|
export * from "./release-ledger";
|
|
15
|
+
export * from "./release-join";
|
|
16
|
+
export * from "./retention-policy";
|
|
17
|
+
export * from "./delivery-metrics";
|
|
18
|
+
export { planLifecycleGc, runLifecycleGc, type GcFile, type GcKept, type GcKeepCode, type GcKeepReason, type GcKind, type GcReport, type LifecycleGcPlan } from "./retention";
|
|
15
19
|
export * from "./build-ledger";
|
|
16
20
|
export * from "./build-ledger-store";
|
|
17
21
|
export * from "./oras-referrer-lookup";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4035: what a partly applied run records, and how a mixed environment is
|
|
3
|
+
* derived from it and the release ledger. Pure; nothing is read or written.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, test } from "vitest";
|
|
7
|
+
import { deployOutcomeOf, parseDeployOutcomes, type DeployOutcome } from "./deploy-outcome";
|
|
8
|
+
import { mixedEnvironment, rollbackTarget, unsettledComponents, type ReleaseCheck } from "./mixed-environment";
|
|
9
|
+
import type { ReleaseRecord } from "./release-ledger";
|
|
10
|
+
import type { PromotionPlan } from "../components/promote";
|
|
11
|
+
|
|
12
|
+
const R1 = `jcs1-sha256:${"1".repeat(64)}`;
|
|
13
|
+
const R2 = `jcs1-sha256:${"2".repeat(64)}`;
|
|
14
|
+
const R3 = `jcs1-sha256:${"3".repeat(64)}`;
|
|
15
|
+
|
|
16
|
+
const rec = (component: string, timestamp: string, releaseDigest?: string): ReleaseRecord => ({
|
|
17
|
+
version: 1, component, env: "prod", digest: `sha256:${component}-${releaseDigest?.slice(12, 13) ?? "x"}`, gitSha: "abc", runId: "r", timestamp, actor: "ci",
|
|
18
|
+
...(releaseDigest ? { releaseDigest } : {}),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const plan: PromotionPlan = {
|
|
22
|
+
from: "staging",
|
|
23
|
+
to: "prod",
|
|
24
|
+
items: ["api", "web"].map((component) => ({ component, digest: `sha256:${component}`, source: rec(component, "2026-01-01T00:00:00.000Z") })),
|
|
25
|
+
notPromoted: [],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const outcome = (timestamp: string, components: DeployOutcome["components"], releaseDigest: string | null = R2): DeployOutcome => ({
|
|
29
|
+
version: 1, env: "prod", verb: "promote", runId: "run-9", timestamp, actor: "bob", releaseDigest, status: "fail", components,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const all = async (): Promise<ReleaseCheck> => ({ kept: true, revoked: false });
|
|
33
|
+
const labels: Record<string, string> = { [R1]: "v1", [R2]: "v2", [R3]: "v3" };
|
|
34
|
+
const label = async (d: string) => labels[d];
|
|
35
|
+
|
|
36
|
+
describe("deployOutcomeOf", () => {
|
|
37
|
+
const run = (statuses: Array<"ok" | "fail" | "gated" | undefined>, status: "ok" | "fail" | "gated") => ({
|
|
38
|
+
status,
|
|
39
|
+
results: statuses.flatMap((s, i) => (s ? [{ component: plan.items[i].component, ok: s === "ok", status: s, records: [] }] : [])),
|
|
40
|
+
});
|
|
41
|
+
const of = (r: ReturnType<typeof run>) => deployOutcomeOf({ verb: "promote", plan, run: r, releaseDigest: R2, runId: "run-9", timestamp: "t", actor: "bob" });
|
|
42
|
+
|
|
43
|
+
test("records a run that deployed some components and not others, or failed", () => {
|
|
44
|
+
expect(of(run(["ok", "fail"], "fail"))?.components.map((c) => [c.component, c.outcome, c.releaseDigest])).toEqual([["api", "deployed", R2], ["web", "failed", R2]]);
|
|
45
|
+
expect(of(run(["fail", undefined], "fail"))?.components.map((c) => c.outcome)).toEqual(["failed", "not-run"]);
|
|
46
|
+
expect(of(run(["ok", "gated"], "gated"))?.status).toBe("gated");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("records nothing for a run that deployed everything, or stopped at a gate before deploying anything", () => {
|
|
50
|
+
expect(of(run(["ok", "ok"], "ok"))).toBeNull();
|
|
51
|
+
expect(of(run(["gated", undefined], "gated"))).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("parseDeployOutcomes", () => {
|
|
56
|
+
test("skips a line that is not an outcome", () => {
|
|
57
|
+
const good = JSON.stringify(outcome("t", [{ component: "api", outcome: "failed", digest: "sha256:a", releaseDigest: null }]));
|
|
58
|
+
expect(parseDeployOutcomes(`${good}\nnot json\n{"version":1}\n`)).toMatchObject({ outcomes: [{ runId: "run-9" }], malformed: 2 });
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("mixed environments", () => {
|
|
63
|
+
test("a failed component stays unsettled until the ledger records it after the run", () => {
|
|
64
|
+
const o = outcome("2026-01-02T00:00:00.000Z", [{ component: "api", outcome: "deployed", digest: "sha256:a", releaseDigest: R2 }, { component: "web", outcome: "failed", digest: "sha256:w", releaseDigest: R2 }]);
|
|
65
|
+
const records = [rec("api", "2026-01-01T00:00:00.000Z", R1), rec("web", "2026-01-01T00:00:00.000Z", R1), rec("api", "2026-01-02T00:00:00.000Z", R2)];
|
|
66
|
+
expect([...unsettledComponents(records, [o]).keys()]).toEqual(["web"]);
|
|
67
|
+
expect(unsettledComponents([...records, rec("web", "2026-01-03T00:00:00.000Z", R1)], [o]).size).toBe(0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("components on different releases are mixed, a record of no release counting as one; no release at all is not", async () => {
|
|
71
|
+
const mixed = await mixedEnvironment({ env: "prod", records: [rec("api", "2026-01-02T00:00:00.000Z", R2), rec("web", "2026-01-01T00:00:00.000Z")], outcomes: [], label, check: all });
|
|
72
|
+
expect(mixed?.components.map((c) => [c.component, c.releaseLabel, c.state])).toEqual([["api", "v2", "settled"], ["web", null, "settled"]]);
|
|
73
|
+
expect(mixed?.rollback).toBeNull();
|
|
74
|
+
expect(await mixedEnvironment({ env: "prod", records: [rec("api", "t1"), rec("web", "t1")], outcomes: [], label, check: all })).toBeNull();
|
|
75
|
+
expect(await mixedEnvironment({ env: "prod", records: [rec("api", "t1", R1), rec("web", "t1", R1)], outcomes: [], label, check: all })).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("a run that failed before deploying anything leaves the environment mixed, and the rollback goes back past the release it tried", async () => {
|
|
79
|
+
const records = [rec("api", "2026-01-01T00:00:00.000Z", R1), rec("api", "2026-01-02T00:00:00.000Z", R2)];
|
|
80
|
+
const o = outcome("2026-01-03T00:00:00.000Z", [{ component: "api", outcome: "failed", digest: "sha256:a3", releaseDigest: R3 }], R3);
|
|
81
|
+
const mixed = await mixedEnvironment({ env: "prod", records, outcomes: [o], label, check: all });
|
|
82
|
+
expect(mixed).toMatchObject({
|
|
83
|
+
components: [{ component: "api", releaseLabel: "v2", state: "failed", attempt: { releaseLabel: "v3" } }],
|
|
84
|
+
redeploy: ["chant components redeploy prod --component api"],
|
|
85
|
+
rollback: "chant components rollback prod --release v2",
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("the rollback target skips a revoked release and one that is no longer kept, and honours before", async () => {
|
|
90
|
+
const records = [rec("api", "2026-01-01T00:00:00.000Z", R1), rec("api", "2026-01-02T00:00:00.000Z", R2), rec("api", "2026-01-03T00:00:00.000Z", R3)];
|
|
91
|
+
const check = async (d: string): Promise<ReleaseCheck> => ({ kept: d !== R2, revoked: false });
|
|
92
|
+
expect(await rollbackTarget({ records, exclude: new Set([R3]), check })).toBe(R1);
|
|
93
|
+
expect(await rollbackTarget({ records, exclude: new Set(), check: async (d) => ({ kept: true, revoked: d === R3 }) })).toBe(R2);
|
|
94
|
+
expect(await rollbackTarget({ records, exclude: new Set(), before: "2026-01-02T00:00:00.000Z", check: all })).toBe(R1);
|
|
95
|
+
expect(await rollbackTarget({ records, exclude: new Set([R1, R2, R3]), check: all })).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
});
|