@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,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stored change sets (#4022): each change-set document a gate binds, kept on
|
|
3
|
+
* `chant/lifecycle` under its digest so the plan an approval bound can be read
|
|
4
|
+
* once the run that planned it is gone.
|
|
5
|
+
*
|
|
6
|
+
* A pull-request gate, a release gate and an `empty` resolution (ws-104
|
|
7
|
+
* question 2 and 6) bind the change-set document's `digest`
|
|
8
|
+
* (`../change-set.ts`) in `planDigest`. Only that value reaches the gate
|
|
9
|
+
* ledger, so this store keeps the document beside it:
|
|
10
|
+
*
|
|
11
|
+
* _change-sets/<digest>.json the document, written once and never rewritten
|
|
12
|
+
*
|
|
13
|
+
* `:` is written as `_` in the file name, as `_plans/`, `_builds/` and
|
|
14
|
+
* `_releases/` do. A workspace member's copy sits under
|
|
15
|
+
* `_members/<member>/_change-sets/` through the same `ledgerDir` prefix the
|
|
16
|
+
* gate ledger uses, so a gate and its change set are read from one member.
|
|
17
|
+
*
|
|
18
|
+
* The stored document is the one the gate bound, with each entry's graph
|
|
19
|
+
* `node` (`../change-set-graph.ts`) and the `graph` it came from. Neither is
|
|
20
|
+
* hashed, so the stored document still verifies against its digest. A
|
|
21
|
+
* component gate in a plain `run --components` binds a lifecycle-plan digest
|
|
22
|
+
* and stores nothing here.
|
|
23
|
+
*
|
|
24
|
+
* `chant lifecycle gc` removes a stored change set from the branch's tree
|
|
25
|
+
* once #4036's retention policy lets it go (./retention.ts); history keeps it.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { sortedJsonReplacer } from "../utils";
|
|
29
|
+
import { CHANGE_SET_CONTRACT, CHANGE_SET_SCHEMA_ID, verifyChangeSetDigest, type ChangeSetDocument } from "../change-set";
|
|
30
|
+
import { ledgerDir, readBlobFromPath, readPathSha, RefCASConflictError, writeLedgerFiles, type LedgerLocation } from "./git";
|
|
31
|
+
|
|
32
|
+
/** The directory on `chant/lifecycle` that holds stored change sets. */
|
|
33
|
+
export const CHANGE_SETS_DIR = "_change-sets";
|
|
34
|
+
|
|
35
|
+
/** The file a change set with `digest` is stored in, inside {@link CHANGE_SETS_DIR}. */
|
|
36
|
+
export function changeSetFile(digest: string): string {
|
|
37
|
+
return `${digest.replace(/:/g, "_")}.json`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Thrown when a document can't be stored: it is not a change-set document, or its content does not hash to its digest. */
|
|
41
|
+
export class ChangeSetStoreError extends Error {
|
|
42
|
+
constructor(message: string) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "ChangeSetStoreError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Store a change-set document under its digest. A document already stored
|
|
50
|
+
* under the digest is never rewritten. Refuses a document whose content does
|
|
51
|
+
* not hash to its own `digest`. Does not push: call `pushLifecycleReport`
|
|
52
|
+
* (./git.ts) after, or let the gate's own pending fact push it.
|
|
53
|
+
*/
|
|
54
|
+
export async function persistChangeSet(
|
|
55
|
+
doc: ChangeSetDocument,
|
|
56
|
+
opts?: { cwd?: string },
|
|
57
|
+
): Promise<{ commit: string | null; written: boolean }> {
|
|
58
|
+
if (doc?.$schema !== CHANGE_SET_SCHEMA_ID || doc.contract !== CHANGE_SET_CONTRACT) {
|
|
59
|
+
throw new ChangeSetStoreError(`not a change-set document: its $schema is not ${CHANGE_SET_SCHEMA_ID} with contract ${CHANGE_SET_CONTRACT}`);
|
|
60
|
+
}
|
|
61
|
+
if (!verifyChangeSetDigest(doc)) {
|
|
62
|
+
throw new ChangeSetStoreError(`change set ${doc.digest} does not hash to its own digest; refusing to store it`);
|
|
63
|
+
}
|
|
64
|
+
const file = changeSetFile(doc.digest);
|
|
65
|
+
if ((await readPathSha(CHANGE_SETS_DIR, file, opts)) !== null) return { commit: null, written: false };
|
|
66
|
+
const dir = await ledgerDir(CHANGE_SETS_DIR, opts);
|
|
67
|
+
try {
|
|
68
|
+
const commit = await writeLedgerFiles(
|
|
69
|
+
[{ path: `${dir}/${file}`, content: `${JSON.stringify(doc, sortedJsonReplacer)}\n`, expectPriorSha: null }],
|
|
70
|
+
`Change set ${doc.digest}`,
|
|
71
|
+
opts,
|
|
72
|
+
);
|
|
73
|
+
return { commit, written: true };
|
|
74
|
+
} catch (err) {
|
|
75
|
+
// Another writer stored the same digest first; what is there is the same document.
|
|
76
|
+
if (err instanceof RefCASConflictError) return { commit: null, written: false };
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** What reading one stored change set found. */
|
|
82
|
+
export type StoredChangeSetRead =
|
|
83
|
+
| { status: "found"; document: ChangeSetDocument }
|
|
84
|
+
| { status: "missing" }
|
|
85
|
+
/** A file is stored under the digest, and it is not a change-set document whose content hashes to that digest. */
|
|
86
|
+
| { status: "malformed"; message: string };
|
|
87
|
+
|
|
88
|
+
/** Parse a stored file, checking it is the document `digest` names. */
|
|
89
|
+
export function parseStoredChangeSet(text: string, digest: string): StoredChangeSetRead {
|
|
90
|
+
let doc: ChangeSetDocument;
|
|
91
|
+
try {
|
|
92
|
+
doc = JSON.parse(text) as ChangeSetDocument;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
return { status: "malformed", message: `the file stored under ${digest} is not JSON: ${err instanceof Error ? err.message : String(err)}` };
|
|
95
|
+
}
|
|
96
|
+
if (!doc || typeof doc !== "object" || doc.$schema !== CHANGE_SET_SCHEMA_ID || doc.contract !== CHANGE_SET_CONTRACT || !Array.isArray(doc.entries) || !Array.isArray(doc.members)) {
|
|
97
|
+
return { status: "malformed", message: `the file stored under ${digest} is not a change-set document` };
|
|
98
|
+
}
|
|
99
|
+
if (doc.digest !== digest || !verifyChangeSetDigest(doc)) {
|
|
100
|
+
return { status: "malformed", message: `the change set stored under ${digest} does not hash to that digest` };
|
|
101
|
+
}
|
|
102
|
+
return { status: "found", document: doc };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Read the change set stored under `digest`. Reads the local `chant/lifecycle`
|
|
107
|
+
* branch and never fetches. Throws only when git itself fails.
|
|
108
|
+
*/
|
|
109
|
+
export async function readStoredChangeSet(digest: string, opts?: LedgerLocation): Promise<StoredChangeSetRead> {
|
|
110
|
+
const text = await readBlobFromPath(CHANGE_SETS_DIR, changeSetFile(digest), opts);
|
|
111
|
+
if (text === null) return { status: "missing" };
|
|
112
|
+
return parseStoredChangeSet(text, digest);
|
|
113
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The commit a run, a gate or a deploy is for (#4020), as one answer for every
|
|
3
|
+
* record that carries it. The checkout's `HEAD` is what the process builds
|
|
4
|
+
* from, so it comes first; a CI job whose checkout has no git (a bare
|
|
5
|
+
* artifact job) falls back to the commit the forge says it started for.
|
|
6
|
+
* Undefined when neither is known, so a record leaves the field out rather
|
|
7
|
+
* than guessing.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ciCommitFromEnv } from "../op/gate-run-locator";
|
|
11
|
+
import { getHeadCommit } from "./git";
|
|
12
|
+
|
|
13
|
+
export async function resolveCommitSha(
|
|
14
|
+
opts: { cwd?: string; env?: Record<string, string | undefined> } = {},
|
|
15
|
+
): Promise<string | undefined> {
|
|
16
|
+
try {
|
|
17
|
+
const head = await getHeadCommit(opts.cwd ? { cwd: opts.cwd } : undefined);
|
|
18
|
+
if (/^[0-9a-f]{40,64}$/.test(head)) return head;
|
|
19
|
+
} catch {
|
|
20
|
+
// Not a checkout, or no git on the path: the environment may still say.
|
|
21
|
+
}
|
|
22
|
+
return ciCommitFromEnv(opts.env);
|
|
23
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four DORA metrics of one environment's deploy records (#4039): how
|
|
3
|
+
* often it deploys, the lead time from commit to running release, the change
|
|
4
|
+
* failure rate and the time to restore.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is pure. The caller reads the ledgers, the release files
|
|
7
|
+
* and the ci tags, and hands this module one environment's records with
|
|
8
|
+
* lookups for the rest; `workspace/delivery-metrics.ts` does that for
|
|
9
|
+
* `chant workspace metrics`.
|
|
10
|
+
*
|
|
11
|
+
* A deploy is one record in `<env>/releases.jsonl`: one component deployed
|
|
12
|
+
* once. A redeploy, which puts the running release back, is counted apart and
|
|
13
|
+
* is not a deploy for the metrics. A deploy fails when, within the failure window after it, the commit
|
|
14
|
+
* it deployed is revoked (a counted `ci/revoked/<sha>` tag, #4033) or the
|
|
15
|
+
* environment is rolled back from it. Which records are rollbacks, and which
|
|
16
|
+
* put the current release back, is decided in {@link rollbackOf} and
|
|
17
|
+
* {@link isRedeploy} and nowhere else, so a new kind of rollback record
|
|
18
|
+
* (#4035) changes those two functions only.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ReleaseRecord, ReleaseRef } from "./release-ledger";
|
|
22
|
+
|
|
23
|
+
/** The failure window when the declaration's `metrics.failureWindowDays` is not set. */
|
|
24
|
+
export const DEFAULT_FAILURE_WINDOW_DAYS = 7;
|
|
25
|
+
|
|
26
|
+
const DAY_MS = 86_400_000;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The earlier deploy a rollback record restores, or null when the record is
|
|
30
|
+
* not a rollback. Today a rollback is a record `chant components rollback`
|
|
31
|
+
* wrote, which names what it restores in `restores` (#2531). Every rule below
|
|
32
|
+
* that asks whether a record rolled an environment back asks this function.
|
|
33
|
+
*/
|
|
34
|
+
export function rollbackOf(record: Pick<ReleaseRecord, "restores">): ReleaseRef | null {
|
|
35
|
+
return record.restores ?? null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether the record put the current release back (`chant components
|
|
40
|
+
* redeploy`, #2604). A redeploy deploys the same release again, so it neither
|
|
41
|
+
* restores a failed deploy nor stands between a deploy and its rollback.
|
|
42
|
+
*/
|
|
43
|
+
export function isRedeploy(record: Pick<ReleaseRecord, "redeploys">): boolean {
|
|
44
|
+
return record.redeploys !== undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A counted revoked tag on a commit, with the time it was made, or null for a lightweight tag. */
|
|
48
|
+
export interface CommitRevocation {
|
|
49
|
+
tag: string;
|
|
50
|
+
tagged: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** What the caller knows beyond the records, as lookups. */
|
|
54
|
+
export interface MetricsLookups {
|
|
55
|
+
/** The release a deploy record names: its `releaseDigest`, else the one its release-gate admission names. Undefined when it names none. */
|
|
56
|
+
releaseOf: (record: ReleaseRecord) => string | undefined;
|
|
57
|
+
/** The commit a release was cut from, or null when its file can't be read here. */
|
|
58
|
+
commitOfRelease: (releaseDigest: string) => string | null;
|
|
59
|
+
/** The release's label, or null. */
|
|
60
|
+
labelOf: (releaseDigest: string) => string | null;
|
|
61
|
+
/** The counted revoked tag on a commit, or null when the commit is not revoked. */
|
|
62
|
+
revocationOf: (commit: string) => CommitRevocation | null;
|
|
63
|
+
/** The commit's committer time in milliseconds, or null when the commit is not in this repository. */
|
|
64
|
+
commitTime: (commit: string) => number | null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The span of time the metrics count deploys in. */
|
|
68
|
+
export interface MetricsWindow {
|
|
69
|
+
/** ISO-8601, inclusive. */
|
|
70
|
+
since: string;
|
|
71
|
+
/** ISO-8601, inclusive. */
|
|
72
|
+
until: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** A record named the way `restores` names one. */
|
|
76
|
+
export interface DeployRef {
|
|
77
|
+
runId: string;
|
|
78
|
+
timestamp: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** One failed deploy, and how it was restored. */
|
|
82
|
+
export interface DeployFailure {
|
|
83
|
+
component: string;
|
|
84
|
+
timestamp: string;
|
|
85
|
+
runId: string;
|
|
86
|
+
/** The release the deploy came from, or null for a deploy made from no release. */
|
|
87
|
+
releaseDigest: string | null;
|
|
88
|
+
label: string | null;
|
|
89
|
+
/** The commit it deployed: the release's commit, or the record's `gitSha` for a deploy made from no release. */
|
|
90
|
+
commit: string;
|
|
91
|
+
/** Why it counts as failed: `revoked`, `rolled-back`, or both. */
|
|
92
|
+
causes: Array<"revoked" | "rolled-back">;
|
|
93
|
+
/** The revoked tag on the commit, when that is a cause. */
|
|
94
|
+
revoked: CommitRevocation | null;
|
|
95
|
+
/** The rollback record that rolled the environment back from it, when that is a cause. */
|
|
96
|
+
rolledBackBy: DeployRef | null;
|
|
97
|
+
/** The next deploy of the component in the environment whose commit is not revoked, or null while there is none. */
|
|
98
|
+
restoredBy: DeployRef | null;
|
|
99
|
+
/** Seconds from the failed deploy to `restoredBy`, or null while it is not restored. */
|
|
100
|
+
restoreSeconds: number | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The four metrics of one environment. */
|
|
104
|
+
export interface EnvironmentMetrics {
|
|
105
|
+
env: string;
|
|
106
|
+
deployments: {
|
|
107
|
+
/** Deploy records in the window, one per component deployed, leaving out redeploys. Rollbacks count. */
|
|
108
|
+
count: number;
|
|
109
|
+
/** Redeploys in the window: records that put the running release back, counted apart from `count`. */
|
|
110
|
+
redeploys: number;
|
|
111
|
+
/** Distinct `runId` values among them, so a run that deploys several components counts once. */
|
|
112
|
+
runs: number;
|
|
113
|
+
/** Of `count`, the deploys made from a release. */
|
|
114
|
+
fromRelease: number;
|
|
115
|
+
/** `count` over the window in weeks, to two decimals. */
|
|
116
|
+
perWeek: number;
|
|
117
|
+
};
|
|
118
|
+
leadTime: {
|
|
119
|
+
/** Changes whose first deploy here falls in the window and whose commit time is known. */
|
|
120
|
+
changes: number;
|
|
121
|
+
/** Of `changes`, releases (the first deploy naming the release). */
|
|
122
|
+
fromRelease: number;
|
|
123
|
+
/** Of `changes`, commits deployed from no release (the first deploy with that `gitSha`). */
|
|
124
|
+
fromCommit: number;
|
|
125
|
+
/** Changes left out because their commit is not in this repository. */
|
|
126
|
+
unknown: number;
|
|
127
|
+
/** The median, in seconds, or null when `changes` is 0. */
|
|
128
|
+
medianSeconds: number | null;
|
|
129
|
+
};
|
|
130
|
+
changeFailure: {
|
|
131
|
+
deployments: number;
|
|
132
|
+
failed: number;
|
|
133
|
+
/** Failed deploys whose commit was revoked within the window after them. */
|
|
134
|
+
revoked: number;
|
|
135
|
+
/** Failed deploys the environment was rolled back from within the window after them. */
|
|
136
|
+
rolledBack: number;
|
|
137
|
+
/** `failed` over `deployments`, to four decimals, or null with no deployments. */
|
|
138
|
+
rate: number | null;
|
|
139
|
+
};
|
|
140
|
+
restore: {
|
|
141
|
+
restored: number;
|
|
142
|
+
open: number;
|
|
143
|
+
medianSeconds: number | null;
|
|
144
|
+
};
|
|
145
|
+
/** The failed deploys in the window, oldest first. */
|
|
146
|
+
failures: DeployFailure[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function median(values: number[]): number | null {
|
|
150
|
+
if (values.length === 0) return null;
|
|
151
|
+
const s = [...values].sort((a, b) => a - b);
|
|
152
|
+
const mid = Math.floor(s.length / 2);
|
|
153
|
+
return Math.round(s.length % 2 === 1 ? s[mid] : (s[mid - 1] + s[mid]) / 2);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const at = (iso: string): number => Date.parse(iso);
|
|
157
|
+
const ref = (r: ReleaseRecord): DeployRef => ({ runId: r.runId, timestamp: r.timestamp });
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The metrics of one environment's records. `records` is the whole ledger of
|
|
161
|
+
* the environment, every component, in ledger order: deploys before the
|
|
162
|
+
* window still decide lead times, and deploys after a failure decide its
|
|
163
|
+
* restore. Only deploys whose `timestamp` falls in `window` are counted.
|
|
164
|
+
*/
|
|
165
|
+
export function environmentMetrics(
|
|
166
|
+
env: string,
|
|
167
|
+
records: readonly ReleaseRecord[],
|
|
168
|
+
lookups: MetricsLookups,
|
|
169
|
+
window: MetricsWindow,
|
|
170
|
+
failureWindowDays: number = DEFAULT_FAILURE_WINDOW_DAYS,
|
|
171
|
+
): EnvironmentMetrics {
|
|
172
|
+
// Ledger order breaks timestamp ties, as `latestPerComponent` does.
|
|
173
|
+
const sorted = records.map((r, i) => ({ r, i })).sort((a, b) => at(a.r.timestamp) - at(b.r.timestamp) || a.i - b.i).map((x) => x.r);
|
|
174
|
+
const since = at(window.since);
|
|
175
|
+
const until = at(window.until);
|
|
176
|
+
const failureMs = failureWindowDays * DAY_MS;
|
|
177
|
+
const inWindow = (r: ReleaseRecord) => at(r.timestamp) >= since && at(r.timestamp) <= until;
|
|
178
|
+
|
|
179
|
+
const facts = sorted.map((r) => {
|
|
180
|
+
const releaseDigest = lookups.releaseOf(r) ?? null;
|
|
181
|
+
const commit = (releaseDigest !== null ? lookups.commitOfRelease(releaseDigest) : null) ?? r.gitSha;
|
|
182
|
+
return { r, releaseDigest, commit, revocation: lookups.revocationOf(commit) };
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// A redeploy puts the running release back: it is counted apart, and
|
|
186
|
+
// neither fails nor restores on its own.
|
|
187
|
+
const deployments = facts.filter((f) => inWindow(f.r) && !isRedeploy(f.r));
|
|
188
|
+
const failures: DeployFailure[] = [];
|
|
189
|
+
for (const [i, f] of facts.entries()) {
|
|
190
|
+
if (!inWindow(f.r) || isRedeploy(f.r)) continue;
|
|
191
|
+
const t = at(f.r.timestamp);
|
|
192
|
+
const later = facts.slice(i + 1).filter((g) => g.r.component === f.r.component);
|
|
193
|
+
|
|
194
|
+
// Revoked within the window after the deploy. A tag with no time, or one
|
|
195
|
+
// made before the deploy, revokes what was deployed all the same.
|
|
196
|
+
const revoked = f.revocation !== null && (f.revocation.tagged === null || at(f.revocation.tagged) - t <= failureMs) ? f.revocation : null;
|
|
197
|
+
|
|
198
|
+
// Rolled back from it: the next deploy of the component that is not a
|
|
199
|
+
// redeploy is a rollback, within the window.
|
|
200
|
+
const next = later.find((g) => !isRedeploy(g.r));
|
|
201
|
+
const rolledBack = next !== undefined && rollbackOf(next.r) !== null && at(next.r.timestamp) - t <= failureMs ? next.r : null;
|
|
202
|
+
|
|
203
|
+
if (!revoked && !rolledBack) continue;
|
|
204
|
+
const restore = later.find((g) => !isRedeploy(g.r) && g.revocation === null);
|
|
205
|
+
failures.push({
|
|
206
|
+
component: f.r.component,
|
|
207
|
+
timestamp: f.r.timestamp,
|
|
208
|
+
runId: f.r.runId,
|
|
209
|
+
releaseDigest: f.releaseDigest,
|
|
210
|
+
label: f.releaseDigest !== null ? lookups.labelOf(f.releaseDigest) : null,
|
|
211
|
+
commit: f.commit,
|
|
212
|
+
causes: [...(revoked ? (["revoked"] as const) : []), ...(rolledBack ? (["rolled-back"] as const) : [])],
|
|
213
|
+
revoked,
|
|
214
|
+
rolledBackBy: rolledBack ? ref(rolledBack) : null,
|
|
215
|
+
restoredBy: restore ? ref(restore.r) : null,
|
|
216
|
+
restoreSeconds: restore ? Math.round((at(restore.r.timestamp) - t) / 1000) : null,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Lead time: one change per release, or per commit deployed from no
|
|
221
|
+
// release, timed at its first deploy in this environment.
|
|
222
|
+
const firstDeploy = new Map<string, (typeof facts)[number]>();
|
|
223
|
+
for (const f of facts) {
|
|
224
|
+
const key = f.releaseDigest !== null ? `release:${f.releaseDigest}` : `commit:${f.r.gitSha}`;
|
|
225
|
+
if (!firstDeploy.has(key)) firstDeploy.set(key, f);
|
|
226
|
+
}
|
|
227
|
+
const leads: number[] = [];
|
|
228
|
+
let fromRelease = 0;
|
|
229
|
+
let unknown = 0;
|
|
230
|
+
for (const f of firstDeploy.values()) {
|
|
231
|
+
if (!inWindow(f.r)) continue;
|
|
232
|
+
const committed = lookups.commitTime(f.commit);
|
|
233
|
+
if (committed === null) {
|
|
234
|
+
unknown++;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
leads.push(Math.max(0, (at(f.r.timestamp) - committed) / 1000));
|
|
238
|
+
if (f.releaseDigest !== null) fromRelease++;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const weeks = Math.max(until - since, DAY_MS) / (7 * DAY_MS);
|
|
242
|
+
const restored = failures.filter((x) => x.restoreSeconds !== null);
|
|
243
|
+
return {
|
|
244
|
+
env,
|
|
245
|
+
deployments: {
|
|
246
|
+
count: deployments.length,
|
|
247
|
+
redeploys: facts.filter((f) => inWindow(f.r) && isRedeploy(f.r)).length,
|
|
248
|
+
runs: new Set(deployments.map((f) => f.r.runId)).size,
|
|
249
|
+
fromRelease: deployments.filter((f) => f.releaseDigest !== null).length,
|
|
250
|
+
perWeek: Math.round((deployments.length / weeks) * 100) / 100,
|
|
251
|
+
},
|
|
252
|
+
leadTime: { changes: leads.length, fromRelease, fromCommit: leads.length - fromRelease, unknown, medianSeconds: median(leads) },
|
|
253
|
+
changeFailure: {
|
|
254
|
+
deployments: deployments.length,
|
|
255
|
+
failed: failures.length,
|
|
256
|
+
revoked: failures.filter((x) => x.revoked !== null).length,
|
|
257
|
+
rolledBack: failures.filter((x) => x.rolledBackBy !== null).length,
|
|
258
|
+
rate: deployments.length === 0 ? null : Math.round((failures.length / deployments.length) * 10_000) / 10_000,
|
|
259
|
+
},
|
|
260
|
+
restore: { restored: restored.length, open: failures.length - restored.length, medianSeconds: median(restored.map((x) => x.restoreSeconds!)) },
|
|
261
|
+
failures,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a promote, rollback or redeploy left behind when it did not deploy
|
|
3
|
+
* every component it planned (#4035).
|
|
4
|
+
*
|
|
5
|
+
* The release ledger records a component only once its deploy succeeded, so
|
|
6
|
+
* after a run that applied partly it still names the earlier release for
|
|
7
|
+
* every component that failed, and nothing says that component may be
|
|
8
|
+
* running part of the new one. A deploy outcome is the record of that run:
|
|
9
|
+
* one line per run in `<env>/outcomes.jsonl` on `chant/lifecycle`, beside
|
|
10
|
+
* the environment's `releases.jsonl` and under the same member prefix,
|
|
11
|
+
* naming each planned component and what happened to it.
|
|
12
|
+
*
|
|
13
|
+
* It is written only when the run ended with some components deployed and
|
|
14
|
+
* others not, or with a component that failed. A run that stopped at a gate
|
|
15
|
+
* before deploying anything changed nothing in the environment; its pending
|
|
16
|
+
* gate is what `workspace status` shows for it.
|
|
17
|
+
*
|
|
18
|
+
* The file is append-only, like the release ledger. Whether the environment
|
|
19
|
+
* is still mixed is derived when it is read ({@link ./mixed-environment.ts}):
|
|
20
|
+
* a component the outcome names as failed or gated stays unsettled until the
|
|
21
|
+
* release ledger has a newer record of it, which a redeploy, a rollback or
|
|
22
|
+
* the same promote run again writes.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { sortedJsonReplacer } from "../utils";
|
|
26
|
+
import type { PromotionPlan } from "../components/promote";
|
|
27
|
+
import type { DriverRunResult } from "../components/driver";
|
|
28
|
+
|
|
29
|
+
/** The file a deploy outcome is appended to, under the environment's directory. */
|
|
30
|
+
export const OUTCOMES_FILE = "outcomes.jsonl";
|
|
31
|
+
|
|
32
|
+
/** What a run did to one component it planned. */
|
|
33
|
+
export type DeployOutcomeState = "deployed" | "failed" | "gated" | "not-run";
|
|
34
|
+
|
|
35
|
+
export interface DeployOutcomeComponent {
|
|
36
|
+
component: string;
|
|
37
|
+
/** `deployed` (a release record was written), `failed`, `gated` (stopped at a gate after the steps before it ran), or `not-run`. */
|
|
38
|
+
outcome: DeployOutcomeState;
|
|
39
|
+
/** The artifact digest the run was deploying. */
|
|
40
|
+
digest: string;
|
|
41
|
+
/** The release the run was deploying for this component, or null when it named none. */
|
|
42
|
+
releaseDigest: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One line of `<env>/outcomes.jsonl`. */
|
|
46
|
+
export interface DeployOutcome {
|
|
47
|
+
version: 1;
|
|
48
|
+
env: string;
|
|
49
|
+
verb: "promote" | "rollback" | "redeploy";
|
|
50
|
+
/** The run id the run's release records carry. */
|
|
51
|
+
runId: string;
|
|
52
|
+
/** The timestamp the run's release records carry. */
|
|
53
|
+
timestamp: string;
|
|
54
|
+
actor: string;
|
|
55
|
+
/** The release the run deployed under (`--release`), or null. */
|
|
56
|
+
releaseDigest: string | null;
|
|
57
|
+
/** How the run ended. */
|
|
58
|
+
status: "fail" | "gated";
|
|
59
|
+
components: DeployOutcomeComponent[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The outcome to record for a run, or null when there is nothing to record:
|
|
64
|
+
* every planned component deployed, or the run stopped at a gate with
|
|
65
|
+
* nothing deployed and nothing failed.
|
|
66
|
+
*/
|
|
67
|
+
export function deployOutcomeOf(input: {
|
|
68
|
+
verb: DeployOutcome["verb"];
|
|
69
|
+
plan: PromotionPlan;
|
|
70
|
+
run: Pick<DriverRunResult, "status" | "results">;
|
|
71
|
+
/** The release the run deployed under, if any. */
|
|
72
|
+
releaseDigest: string | null;
|
|
73
|
+
runId: string;
|
|
74
|
+
timestamp: string;
|
|
75
|
+
actor: string;
|
|
76
|
+
}): DeployOutcome | null {
|
|
77
|
+
const { plan, run } = input;
|
|
78
|
+
if (run.status === "ok") return null;
|
|
79
|
+
const components: DeployOutcomeComponent[] = plan.items.map((item) => {
|
|
80
|
+
const result = run.results.find((r) => r.component === item.component);
|
|
81
|
+
const outcome: DeployOutcomeState = !result ? "not-run" : result.status === "ok" ? "deployed" : result.status === "gated" ? "gated" : "failed";
|
|
82
|
+
return {
|
|
83
|
+
component: item.component,
|
|
84
|
+
outcome,
|
|
85
|
+
digest: item.digest,
|
|
86
|
+
releaseDigest: input.releaseDigest ?? item.source.releaseDigest ?? null,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
const deployed = components.some((c) => c.outcome === "deployed");
|
|
90
|
+
const failed = components.some((c) => c.outcome === "failed");
|
|
91
|
+
if (!deployed && !failed) return null;
|
|
92
|
+
return {
|
|
93
|
+
version: 1,
|
|
94
|
+
env: plan.to,
|
|
95
|
+
verb: input.verb,
|
|
96
|
+
runId: input.runId,
|
|
97
|
+
timestamp: input.timestamp,
|
|
98
|
+
actor: input.actor,
|
|
99
|
+
releaseDigest: input.releaseDigest,
|
|
100
|
+
status: run.status,
|
|
101
|
+
components,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const STATES: readonly string[] = ["deployed", "failed", "gated", "not-run"];
|
|
106
|
+
|
|
107
|
+
function isOutcome(v: unknown): v is DeployOutcome {
|
|
108
|
+
const o = v as Partial<DeployOutcome> | null;
|
|
109
|
+
return (
|
|
110
|
+
o !== null &&
|
|
111
|
+
typeof o === "object" &&
|
|
112
|
+
o.version === 1 &&
|
|
113
|
+
typeof o.env === "string" &&
|
|
114
|
+
typeof o.runId === "string" &&
|
|
115
|
+
typeof o.timestamp === "string" &&
|
|
116
|
+
Array.isArray(o.components) &&
|
|
117
|
+
o.components.every((c) => c && typeof c.component === "string" && STATES.includes(c.outcome) && typeof c.digest === "string")
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Parse the lines of an `outcomes.jsonl`, oldest first. A line that is not an outcome is counted and skipped. */
|
|
122
|
+
export function parseDeployOutcomes(text: string | null): { outcomes: DeployOutcome[]; malformed: number } {
|
|
123
|
+
const outcomes: DeployOutcome[] = [];
|
|
124
|
+
let malformed = 0;
|
|
125
|
+
for (const line of (text ?? "").split("\n").map((l) => l.trim()).filter(Boolean)) {
|
|
126
|
+
try {
|
|
127
|
+
const parsed = JSON.parse(line) as unknown;
|
|
128
|
+
if (isOutcome(parsed)) outcomes.push(parsed);
|
|
129
|
+
else malformed++;
|
|
130
|
+
} catch {
|
|
131
|
+
malformed++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { outcomes, malformed };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Append one outcome to `<env>/outcomes.jsonl`. Does not push; the caller pushes with the release records. */
|
|
138
|
+
export async function appendDeployOutcome(outcome: DeployOutcome, opts?: { cwd?: string }): Promise<string> {
|
|
139
|
+
const { appendLedgerLine } = await import("./git");
|
|
140
|
+
return appendLedgerLine(outcome.env, OUTCOMES_FILE, JSON.stringify(outcome, sortedJsonReplacer), "Deploy outcome", opts);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Every outcome recorded for `env`, oldest first, read where `readReleaseLedger` reads; `[]` when none is. */
|
|
144
|
+
export async function readDeployOutcomes(env: string, opts?: import("./git").LedgerLocation): Promise<DeployOutcome[]> {
|
|
145
|
+
const { readBlobFromPath } = await import("./git");
|
|
146
|
+
return parseDeployOutcomes(await readBlobFromPath(env, OUTCOMES_FILE, opts)).outcomes;
|
|
147
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stored change sets mapped onto a real graph (#4022).
|
|
3
|
+
*
|
|
4
|
+
* The estate is the aws source `__fixtures__/change-set/lifecycle-v2.ts.txt`
|
|
5
|
+
* as a workspace member, and the change set is
|
|
6
|
+
* `__fixtures__/change-set/lifecycle-plan.floci.json`, chant's real plan of
|
|
7
|
+
* that source against floci (see `../change-set.test.ts`). The graph is the
|
|
8
|
+
* member's own `chant graph --format ir`, run by `storeGateChangeSet` as a
|
|
9
|
+
* pull-request apply runs it, and the ids are checked against `chant
|
|
10
|
+
* workspace graph`, which is what behold reads. The stored document is then
|
|
11
|
+
* read back through `chant change-set show --json`, and the gate that binds
|
|
12
|
+
* it through `workspace status`'s gate reader.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
20
|
+
import { composeChangeSet, lifecyclePlanPart } from "../change-set";
|
|
21
|
+
import type { ChangeSet as LifecycleChangeSet } from "./change-set";
|
|
22
|
+
import { storeGateChangeSet } from "./gate-change-set";
|
|
23
|
+
import { appendPendingGate } from "./gate-ledger";
|
|
24
|
+
import { clearMemberLedgerCache } from "./member-ledger";
|
|
25
|
+
import { readMemberGates } from "../workspace/status-gates";
|
|
26
|
+
|
|
27
|
+
const REPO = realpathSync(join(import.meta.dirname, "..", "..", "..", ".."));
|
|
28
|
+
const LOADER = join(REPO, "node_modules", "tsx", "dist", "loader.mjs");
|
|
29
|
+
const MAIN = join(REPO, "packages", "core", "src", "cli", "main.ts");
|
|
30
|
+
const FIXTURES = join(import.meta.dirname, "..", "__fixtures__", "change-set");
|
|
31
|
+
const TIMEOUT = 240_000;
|
|
32
|
+
|
|
33
|
+
function chant(cwd: string, ...args: string[]): { code: number | null; stdout: string; stderr: string } {
|
|
34
|
+
const r = spawnSync(process.execPath, ["--import", `file://${LOADER}`, MAIN, ...args], { cwd, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
|
|
35
|
+
return { code: r.status, stdout: r.stdout, stderr: r.stderr };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe("a change set stored by a gate, on a real estate's graph", () => {
|
|
39
|
+
let root: string;
|
|
40
|
+
let member: string;
|
|
41
|
+
const git = (...a: string[]) => {
|
|
42
|
+
const r = spawnSync("git", a, { cwd: root, encoding: "utf8" });
|
|
43
|
+
if (r.status !== 0) throw new Error(`git ${a.join(" ")}: ${r.stderr}`);
|
|
44
|
+
return r.stdout.trim();
|
|
45
|
+
};
|
|
46
|
+
const plan = JSON.parse(readFileSync(join(FIXTURES, "lifecycle-plan.floci.json"), "utf8")) as LifecycleChangeSet;
|
|
47
|
+
const doc = composeChangeSet([lifecyclePlanPart({ member: "queues", plan })]);
|
|
48
|
+
|
|
49
|
+
beforeAll(() => {
|
|
50
|
+
root = realpathSync(mkdtempSync(join(tmpdir(), "chant-change-set-graph-")));
|
|
51
|
+
member = join(root, "delivery");
|
|
52
|
+
mkdirSync(member);
|
|
53
|
+
symlinkSync(join(REPO, "node_modules"), join(root, "node_modules"));
|
|
54
|
+
writeFileSync(join(root, "chant.workspace.json"), JSON.stringify({ name: "acme", schema: 1, members: [{ name: "delivery", dir: "delivery", kind: "chant" }] }));
|
|
55
|
+
writeFileSync(join(member, "chant.config.json"), JSON.stringify({ lexicons: ["aws"] }));
|
|
56
|
+
writeFileSync(join(member, "queues.ts"), readFileSync(join(FIXTURES, "lifecycle-v2.ts.txt"), "utf8"));
|
|
57
|
+
writeFileSync(join(root, ".gitignore"), "node_modules\n");
|
|
58
|
+
git("init", "-q", "-b", "main");
|
|
59
|
+
git("config", "user.email", "ci@example.test");
|
|
60
|
+
git("config", "user.name", "ci");
|
|
61
|
+
git("add", "-A");
|
|
62
|
+
git("commit", "-qm", "queues");
|
|
63
|
+
clearMemberLedgerCache();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterAll(() => {
|
|
67
|
+
if (root) rmSync(root, { recursive: true, force: true });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("each entry carries the node chant workspace graph gives its address", async () => {
|
|
71
|
+
const head = git("rev-parse", "HEAD");
|
|
72
|
+
const stored = await storeGateChangeSet({ doc, cwd: member, at: head });
|
|
73
|
+
expect(stored.warning).toBeUndefined();
|
|
74
|
+
expect(stored).toMatchObject({ digest: doc.digest, written: true, graph: { member: "delivery", at: head } });
|
|
75
|
+
|
|
76
|
+
const graph = chant(root, "workspace", "graph");
|
|
77
|
+
expect(graph.code, graph.stderr).toBe(0);
|
|
78
|
+
const nodeIds = new Set((JSON.parse(graph.stdout) as { nodes: Array<{ id: string }> }).nodes.map((n) => n.id));
|
|
79
|
+
|
|
80
|
+
const shown = chant(member, "change-set", "show", doc.digest, "--json");
|
|
81
|
+
expect(shown.code, shown.stderr).toBe(0);
|
|
82
|
+
const out = JSON.parse(shown.stdout);
|
|
83
|
+
expect(out).toMatchObject({ contract: 1, member: "delivery", digest: doc.digest, reason: null });
|
|
84
|
+
expect(out.changeSet.digest).toBe(doc.digest);
|
|
85
|
+
expect(out.changeSet.graph).toEqual({ member: "delivery", at: head });
|
|
86
|
+
const nodes = Object.fromEntries((out.changeSet.entries as Array<{ address: string; node: string | null }>).map((e) => [e.address, e.node]));
|
|
87
|
+
// jobs, events and dlq are declared; alerts is a live topic v2 no longer declares, so it has no node.
|
|
88
|
+
expect(nodes).toEqual({ alerts: null, dlq: "delivery/dlq", events: "delivery/events", jobs: "delivery/jobs" });
|
|
89
|
+
for (const node of Object.values(nodes)) if (node !== null) expect(nodeIds.has(node), node).toBe(true);
|
|
90
|
+
|
|
91
|
+
// From the workspace root, by --member.
|
|
92
|
+
const fromRoot = chant(root, "change-set", "show", doc.digest, "--member", "delivery", "--json");
|
|
93
|
+
expect(JSON.parse(fromRoot.stdout)).toEqual(out);
|
|
94
|
+
|
|
95
|
+
// Without --json: the grouped summary, then each change on its node.
|
|
96
|
+
const text = chant(member, "change-set", "show", doc.digest);
|
|
97
|
+
expect(text.code, text.stderr).toBe(0);
|
|
98
|
+
expect(text.stdout).toContain(`Nodes from the graph of delivery at ${head.slice(0, 12)}:`);
|
|
99
|
+
expect(text.stdout).toMatch(/create\s+queues {2}dlq {2}delivery\/dlq/);
|
|
100
|
+
}, TIMEOUT);
|
|
101
|
+
|
|
102
|
+
test("the gate that binds it links to it in workspace status", async () => {
|
|
103
|
+
const now = new Date().toISOString();
|
|
104
|
+
const later = new Date(Date.now() + 3_600_000).toISOString();
|
|
105
|
+
await appendPendingGate({ op: "pr-7-delivery", gate: "pr-apply", planDigest: doc.digest, timestamp: now, expiresAt: later }, { cwd: member });
|
|
106
|
+
const { gates } = await readMemberGates("_members/delivery/_gates", "members", git("rev-parse", "chant/lifecycle"), [], root, now);
|
|
107
|
+
expect(gates).toEqual([expect.objectContaining({ component: "pr-7-delivery", state: "pending", planDigest: doc.digest, changeSet: doc.digest })]);
|
|
108
|
+
|
|
109
|
+
const status = chant(root, "workspace", "status", "dev", "--json");
|
|
110
|
+
expect(status.stdout, status.stderr).not.toBe("");
|
|
111
|
+
const gate = (JSON.parse(status.stdout).members as Array<{ name: string; gates?: Array<{ component: string; changeSet?: string }> }>)
|
|
112
|
+
.find((m) => m.name === "delivery")
|
|
113
|
+
?.gates?.find((g) => g.component === "pr-7-delivery");
|
|
114
|
+
expect(gate?.changeSet, status.stderr).toBe(doc.digest);
|
|
115
|
+
}, TIMEOUT);
|
|
116
|
+
|
|
117
|
+
test("a digest nothing is stored under shows no change set and exits 1", () => {
|
|
118
|
+
const missing = chant(member, "change-set", "show", `jcs1-sha256:${"0".repeat(64)}`, "--json");
|
|
119
|
+
expect(missing.code).toBe(1);
|
|
120
|
+
expect(JSON.parse(missing.stdout)).toMatchObject({ member: "delivery", changeSet: null, reason: null });
|
|
121
|
+
}, TIMEOUT);
|
|
122
|
+
});
|