@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,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The why chain of `graph --intent` (#4023), over a workspace in a throwaway
|
|
3
|
+
* git repository whose `chant/lifecycle` ledgers are written through the
|
|
4
|
+
* library, so the test needs no deploy. The end-to-end chain, with a real
|
|
5
|
+
* cut, a gated `components promote --release` and an approval, is in
|
|
6
|
+
* cli/handlers/intent-trace.e2e.test.ts.
|
|
7
|
+
*
|
|
8
|
+
* - c1 adds the workspace, the work kind, W-001 (constrains app/server.mjs)
|
|
9
|
+
* and the server.
|
|
10
|
+
* - c2 changes the server with `Chant-Record: work:W-001`: linked, green, and
|
|
11
|
+
* release v1 is cut from it and deployed to prod through the release gate.
|
|
12
|
+
* - c3 changes the server with no trailer: unlinked, in W-001's window, and
|
|
13
|
+
* revoked. A plain deploy of it reaches dev through a component gate, with
|
|
14
|
+
* a release plan in `_plans/`.
|
|
15
|
+
* - c4 changes the README: green, release v2 is cut from it, so v2 contains
|
|
16
|
+
* c2 and c3. Its staging deploy names no releaseDigest and joins v2
|
|
17
|
+
* through its release gate admission.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
23
|
+
import { appendGateResolution } from "../lifecycle/gate-ledger";
|
|
24
|
+
import { persistReleasePlan } from "../lifecycle/plan-ledger";
|
|
25
|
+
import { appendReleaseRecord } from "../lifecycle/release-ledger";
|
|
26
|
+
import { appendCut, makeRelease, persistRelease, type Release } from "../lifecycle/release-store";
|
|
27
|
+
import { cleanScratch, contract, git, REPO, repo, writeFiles } from "./__fixtures__/contract-repo";
|
|
28
|
+
import { intentGraph, type IntentDocument } from "./intent";
|
|
29
|
+
import intentSchema from "./intent.schema.json";
|
|
30
|
+
import { traceLines } from "./intent-cli";
|
|
31
|
+
|
|
32
|
+
const REF = join(REPO, "reference-workspace");
|
|
33
|
+
const KIND_FILES = {
|
|
34
|
+
"work/work.kind.mjs": readFileSync(join(REF, "work", "work.kind.mjs"), "utf-8"),
|
|
35
|
+
"work/work.schema.json": readFileSync(join(REF, "work", "work.schema.json"), "utf-8"),
|
|
36
|
+
"decisions/decision.kind.mjs": readFileSync(join(REF, "decisions", "decision.kind.mjs"), "utf-8"),
|
|
37
|
+
"decisions/decision.schema.json": readFileSync(join(REF, "decisions", "decision.schema.json"), "utf-8"),
|
|
38
|
+
};
|
|
39
|
+
const W001 = `---\n${JSON.stringify({ schema: 1, id: "W-001", title: "Serve on 8081", state: "open", implements: [], needs: [], constrains: ["path:app/server.mjs"], evidence: [], opened_on: "2026-10-16", supersedes: [] }, null, 2)}\n---\n\n# W-001\n`;
|
|
40
|
+
const DECLARATION = JSON.stringify({ name: "shop", schema: 1, members: [{ name: "app", dir: "app", kind: "other", because: "a plain Node server" }] });
|
|
41
|
+
const PLAN = `jcs1-sha256:${"b".repeat(64)}`;
|
|
42
|
+
const DEV_PLAN = `sha256:${"0123456789abcdef".repeat(4)}`;
|
|
43
|
+
|
|
44
|
+
let root: string;
|
|
45
|
+
let app: string;
|
|
46
|
+
const sha: Record<string, string> = {};
|
|
47
|
+
let v1: Release;
|
|
48
|
+
let v2: Release;
|
|
49
|
+
|
|
50
|
+
function commit(name: string, message: string): void {
|
|
51
|
+
git(root, "add", "-A");
|
|
52
|
+
git(root, "commit", "-q", "-m", message);
|
|
53
|
+
sha[name] = git(root, "rev-parse", "HEAD");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const subject = (commitSha: string) => ({ member: "app", commit: commitSha, artifactDigests: [{ component: "api", kind: "image" as const, path: "api.tar", digest: `sha256:${commitSha.slice(0, 8).padEnd(64, "0")}` }], targets: [{ env: "prod", templates: [], params: [] }] });
|
|
57
|
+
|
|
58
|
+
beforeAll(async () => {
|
|
59
|
+
root = repo({ "chant.workspace.json": DECLARATION, "app/server.mjs": "export const port = 8080;\n", "README.md": "shop\n", "work/W-001-port.md": W001, ...KIND_FILES });
|
|
60
|
+
app = join(root, "app");
|
|
61
|
+
commit("c1", "the shop and W-001");
|
|
62
|
+
writeFiles(root, { "app/server.mjs": "export const port = 8081;\n" });
|
|
63
|
+
commit("c2", "serve on 8081\n\nChant-Record: work:W-001");
|
|
64
|
+
writeFiles(root, { "app/server.mjs": "export const port = 8081;\nexport const host = '0.0.0.0';\n" });
|
|
65
|
+
commit("c3", "listen on every interface");
|
|
66
|
+
writeFiles(root, { "README.md": "shop, on 8081\n" });
|
|
67
|
+
commit("c4", "say the port");
|
|
68
|
+
git(root, "tag", `ci/green/${sha.c2}`, sha.c2);
|
|
69
|
+
git(root, "tag", `ci/revoked/${sha.c3}`, sha.c3);
|
|
70
|
+
git(root, "tag", `ci/green/${sha.c4}`, sha.c4);
|
|
71
|
+
|
|
72
|
+
const loc = { cwd: app };
|
|
73
|
+
v1 = makeRelease(subject(sha.c2));
|
|
74
|
+
v2 = makeRelease(subject(sha.c4));
|
|
75
|
+
for (const [r, label, at] of [[v1, "v1", "2026-10-16T10:00:00.000Z"], [v2, "v2", "2026-10-16T12:00:00.000Z"]] as const) {
|
|
76
|
+
await persistRelease(r, [], loc);
|
|
77
|
+
await appendCut({ releaseDigest: r.releaseDigest, label, actor: "alice", timestamp: at, runOrigin: null }, loc);
|
|
78
|
+
}
|
|
79
|
+
await appendGateResolution({ op: "release", gate: "prod-release", resolvedBy: "bob", origin: "cli", timestamp: "2026-10-16T10:30:00.000Z", planDigest: PLAN, environment: "prod", releaseDigest: v1.releaseDigest }, loc);
|
|
80
|
+
await appendGateResolution({ op: "release", gate: "staging-release", resolvedBy: "carol", origin: "mcp", timestamp: "2026-10-16T12:30:00.000Z", planDigest: PLAN, environment: "staging", releaseDigest: v2.releaseDigest }, loc);
|
|
81
|
+
await appendGateResolution({ op: "api", gate: "dev-gate", resolvedBy: "dave", timestamp: "2026-10-16T11:00:00.000Z", planDigest: DEV_PLAN, environment: "dev" }, loc);
|
|
82
|
+
await persistReleasePlan({ digest: DEV_PLAN, work: ["W-001"] }, loc);
|
|
83
|
+
const deploy = (env: string, gitSha: string, runId: string, timestamp: string, extra: Record<string, unknown>) =>
|
|
84
|
+
appendReleaseRecord({ component: "api", env, digest: DEV_PLAN, gitSha, runId, timestamp, actor: "ci", ...extra }, loc);
|
|
85
|
+
await deploy("prod", sha.c2, "run-1", "2026-10-16T10:40:00.000Z", { releaseDigest: v1.releaseDigest, approver: "bob", admission: { op: "release", gate: "prod-release", resolvedAt: "2026-10-16T10:30:00.000Z" } });
|
|
86
|
+
await deploy("dev", sha.c3, "run-2", "2026-10-16T11:10:00.000Z", { approver: "dave", admission: { op: "api", gate: "dev-gate", resolvedAt: "2026-10-16T11:00:00.000Z" } });
|
|
87
|
+
await deploy("staging", sha.c4, "run-3", "2026-10-16T12:40:00.000Z", { approver: "carol", admission: { op: "release", gate: "staging-release", resolvedAt: "2026-10-16T12:30:00.000Z" } });
|
|
88
|
+
}, 30_000);
|
|
89
|
+
afterAll(cleanScratch);
|
|
90
|
+
|
|
91
|
+
const intent = contract(intentSchema);
|
|
92
|
+
type Result = Exclude<IntentDocument, { error: unknown }>;
|
|
93
|
+
|
|
94
|
+
async function walk(region: string): Promise<Result> {
|
|
95
|
+
const { doc } = await intentGraph({ cwd: root, region, kinds: ["work/work.kind.mjs", "decisions/decision.kind.mjs"].map((k) => join(root, k)) });
|
|
96
|
+
intent.expectValid(doc);
|
|
97
|
+
if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
|
|
98
|
+
return doc;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
describe("the why chain (#4023)", () => {
|
|
102
|
+
test("a file region walks each commit to its work item, tag, release, gate and deploys, each link with its source", async () => {
|
|
103
|
+
const doc = await walk("app/server.mjs");
|
|
104
|
+
const trace = doc.trace!;
|
|
105
|
+
expect(trace.reasons).toEqual([]);
|
|
106
|
+
expect(trace.commits.map((c) => c.sha)).toEqual([sha.c3, sha.c2, sha.c1]);
|
|
107
|
+
const [c3, c2] = trace.commits;
|
|
108
|
+
|
|
109
|
+
// Who asked for it: the trailer links c2 to W-001; c3 only falls in its window.
|
|
110
|
+
expect(c2.work).toEqual({ state: "linked", items: [{ id: "work:W-001", node: "record:work/W-001", source: { ref: sha.c2, field: "Chant-Record", value: "work:W-001" } }], problems: [] });
|
|
111
|
+
expect(c3.work).toMatchObject({ state: "unlinked", items: [] });
|
|
112
|
+
expect(c3.windowMatches).toEqual([{ id: "work:W-001", node: "record:work/W-001", source: { ref: "work/W-001-port.md", field: "window", value: "worked" } }]);
|
|
113
|
+
|
|
114
|
+
// The ci tags.
|
|
115
|
+
expect(c2.tag).toMatchObject({ state: "green", green: { tag: `ci/green/${sha.c2}`, source: { ref: `refs/tags/ci/green/${sha.c2}` } }, revoked: null });
|
|
116
|
+
expect(c3.tag).toMatchObject({ state: "revoked", revoked: { tag: `ci/revoked/${sha.c3}` } });
|
|
117
|
+
|
|
118
|
+
// The release cut from c2, and the first release that contains c3.
|
|
119
|
+
expect(c2.releases).toEqual([{ release: v1.releaseDigest, link: "cut-from", source: { ref: `chant/lifecycle:_members/app/_releases/${v1.releaseDigest.replace(/:/g, "_")}.json`, field: "commit", value: sha.c2 } }]);
|
|
120
|
+
expect(c3.releases).toEqual([expect.objectContaining({ release: v2.releaseDigest, link: "contains", source: expect.objectContaining({ value: sha.c4 }) })]);
|
|
121
|
+
|
|
122
|
+
// The first deploy per environment that shipped each commit.
|
|
123
|
+
expect(c2.deploys.map((d) => [d.env, d.link])).toEqual([["dev", "contains"], ["prod", "built-from"], ["staging", "contains"]]);
|
|
124
|
+
expect(c3.deploys.map((d) => [d.env, d.link])).toEqual([["dev", "built-from"], ["staging", "contains"]]);
|
|
125
|
+
|
|
126
|
+
// Who let it ship: the release gate's resolution, with resolver, channel, time and plan.
|
|
127
|
+
const r1 = trace.releases.find((r) => r.releaseDigest === v1.releaseDigest)!;
|
|
128
|
+
expect(r1).toMatchObject({ label: "v1", commit: sha.c2, tag: { state: "green" }, cuts: [{ label: "v1", actor: "alice", signed: false }] });
|
|
129
|
+
expect(r1.gates).toEqual([
|
|
130
|
+
expect.objectContaining({ op: "release", gate: "prod-release", environment: "prod", resolvedBy: "bob", channel: "cli", at: "2026-10-16T10:30:00.000Z", planDigest: PLAN, changeSet: null, source: { ref: "chant/lifecycle:_members/app/_gates/release.jsonl", field: "releaseDigest", value: v1.releaseDigest } }),
|
|
131
|
+
]);
|
|
132
|
+
const prod = trace.deploys.find((d) => d.env === "prod")!;
|
|
133
|
+
expect(r1.deploys).toEqual([prod.id]);
|
|
134
|
+
expect(prod).toMatchObject({ release: v1.releaseDigest, releaseSource: { field: "releaseDigest" }, admittedBy: { gate: "prod-release", resolvedBy: "bob" }, source: { ref: "chant/lifecycle:_members/app/prod/releases.jsonl", field: "runId", value: "run-1" } });
|
|
135
|
+
|
|
136
|
+
// A deploy with no releaseDigest joins its release through its release gate admission (#4043).
|
|
137
|
+
const staging = trace.deploys.find((d) => d.env === "staging")!;
|
|
138
|
+
expect(staging).toMatchObject({ release: v2.releaseDigest, releaseSource: { field: "admission", value: "staging-release@2026-10-16T12:30:00.000Z" }, admittedBy: { resolvedBy: "carol", channel: "mcp" } });
|
|
139
|
+
expect(trace.releases.find((r) => r.releaseDigest === v2.releaseDigest)!.deploys).toEqual([staging.id]);
|
|
140
|
+
|
|
141
|
+
// A plain deploy names no release, and its component gate and release plan (ws-055) are read back.
|
|
142
|
+
const dev = trace.deploys.find((d) => d.env === "dev")!;
|
|
143
|
+
expect(dev).toMatchObject({ release: null, admittedBy: { op: "api", gate: "dev-gate", resolvedBy: "dave", planDigest: DEV_PLAN }, plan: { document: { digest: DEV_PLAN, work: ["W-001"] }, source: { field: "digest" } } });
|
|
144
|
+
|
|
145
|
+
expect(traceLines(trace)).toEqual(
|
|
146
|
+
expect.arrayContaining([
|
|
147
|
+
expect.stringMatching(new RegExp(`^trace ${sha.c2.slice(0, 8)} work work:W-001 \\(Chant-Record\\); in the window of work:W-001; green; released as v1; dev since`)),
|
|
148
|
+
expect.stringMatching(/^release v1 from .*green; cut by alice/),
|
|
149
|
+
expect.stringMatching(/^ gate release\/prod-release prod: bob on cli at 2026-10-16T10:30:00.000Z/),
|
|
150
|
+
expect.stringMatching(/^deploy dev api .* no release; admitted by api\/dev-gate dev: dave/),
|
|
151
|
+
]),
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("a commit sha is a region: the chain of that one commit, from the files it changed", async () => {
|
|
156
|
+
const doc = await walk(sha.c2.slice(0, 10));
|
|
157
|
+
expect(doc.region).toBe(`region:commit:${sha.c2}`);
|
|
158
|
+
expect(doc.nodes[0]).toMatchObject({ kind: "region", type: "commit", path: ".", commit: sha.c2, member: "app" });
|
|
159
|
+
expect(doc.history.follows).toBe("commit");
|
|
160
|
+
expect(doc.trace!.commits.map((c) => [c.sha, c.work?.state, c.tag.state, c.releases.map((r) => r.link)])).toEqual([[sha.c2, "linked", "green", ["cut-from"]]]);
|
|
161
|
+
// W-001 constrains a file the commit changed, so the work node is there and the commit falls in its window.
|
|
162
|
+
expect(doc.trace!.commits[0].windowMatches.map((w) => w.id)).toEqual(["work:W-001"]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("a commit no release was cut from is shipped by the first release that contains it", async () => {
|
|
166
|
+
const doc = await walk("README.md");
|
|
167
|
+
expect(doc.trace!.commits.map((c) => [c.sha, c.work?.state, c.tag.state, c.releases.map((r) => [r.release, r.link])])).toEqual([
|
|
168
|
+
[sha.c4, "unlinked", "green", [[v2.releaseDigest, "cut-from"]]],
|
|
169
|
+
[sha.c1, "unlinked", "none", [[v1.releaseDigest, "contains"]]],
|
|
170
|
+
]);
|
|
171
|
+
expect(doc.trace!.commits[1].deploys.map((d) => [d.env, d.link])).toEqual([["dev", "contains"], ["prod", "contains"], ["staging", "contains"]]);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The why chain of a region (#4023): for each commit the intent walk read,
|
|
3
|
+
* the work item it names, its ci tag, the release that shipped it, the gate
|
|
4
|
+
* resolutions that admitted that release and the deploy records per
|
|
5
|
+
* environment. "Who asked for this, and who let it ship" in one read.
|
|
6
|
+
*
|
|
7
|
+
* Every link is made by a record that already exists, and each one states
|
|
8
|
+
* that record in `source`, so a reader can follow it:
|
|
9
|
+
*
|
|
10
|
+
* - work item: a `Chant-Record` or `Chant-Lease` trailer, judged as #4029's
|
|
11
|
+
* `judgeCommitLink` judges it. A commit in a work item's window (the
|
|
12
|
+
* intent graph's `worked` bucket) is listed apart, as a window match, and
|
|
13
|
+
* never links it.
|
|
14
|
+
* - ci tag: `ci/green/<sha>` or `ci/revoked/<sha>`, judged as `release cut`
|
|
15
|
+
* judges it (#4031, `judgedCiTags`).
|
|
16
|
+
* - release: `_releases/<digest>.json`, whose `commit` is the commit itself
|
|
17
|
+
* (`cut-from`) or one that has it in its history (`contains`).
|
|
18
|
+
* - gates and deploys: `joinRelease` (#4043), and a deploy's `admission`
|
|
19
|
+
* read back to the resolution in `_gates/<op>.jsonl` (#4020).
|
|
20
|
+
* - the plan a gate bound: the change set stored under its `planDigest`
|
|
21
|
+
* (#4022), and a deploy's release plan in `_plans/` (ws-055).
|
|
22
|
+
*
|
|
23
|
+
* A commit is "contained" by a later commit when that commit has it as an
|
|
24
|
+
* ancestor in the history read. For each commit the chain names the releases
|
|
25
|
+
* cut from it, or failing those the first release cut after it, and the first
|
|
26
|
+
* deploy per environment that shipped it, so the document stays the size of
|
|
27
|
+
* the walk. Everything is read from local git: the tags and the local
|
|
28
|
+
* `chant/lifecycle` branch. Nothing is fetched.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { execFileSync } from "node:child_process";
|
|
32
|
+
import type { GateResolutionRecord } from "../lifecycle/gate-ledger";
|
|
33
|
+
import type { ReleaseRecord, RunOrigin } from "../lifecycle/release-ledger";
|
|
34
|
+
import type { JoinedRelease } from "../lifecycle/release-join";
|
|
35
|
+
import type { ReleasePlan } from "../lifecycle/plan-ledger";
|
|
36
|
+
import type { Release, ReleaseCut } from "../lifecycle/release-store";
|
|
37
|
+
import type { CiTags, CountedCiTag, IgnoredCiTag, RevokedTagView } from "./ci-green";
|
|
38
|
+
import type { CommitLinkProblem, CommitLinkState, WorkIndex } from "./commit-links";
|
|
39
|
+
import type { CiGreen } from "./declaration";
|
|
40
|
+
import type { CommitTrailerJoins } from "./trailer-joins";
|
|
41
|
+
|
|
42
|
+
/** Where a link comes from: the record (`ref`), and the field of it whose `value` made the join. */
|
|
43
|
+
export interface TraceSource {
|
|
44
|
+
/** A commit sha, a tag ref (`refs/tags/...`), a record file, or a path on `chant/lifecycle` (`chant/lifecycle:<path>`). */
|
|
45
|
+
ref: string;
|
|
46
|
+
/** The trailer, field or edge that joins, such as `Chant-Record`, `commit`, `releaseDigest`, `admission` or `window`. */
|
|
47
|
+
field: string;
|
|
48
|
+
/** Its value, as the record holds it. */
|
|
49
|
+
value: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A work item a commit links to, or falls in the window of. */
|
|
53
|
+
export interface TraceWork {
|
|
54
|
+
/** `<kind>:<id>`. */
|
|
55
|
+
id: string;
|
|
56
|
+
/** The work node in `nodes`, or null when no kind read has the record. */
|
|
57
|
+
node: string | null;
|
|
58
|
+
source: TraceSource;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface TraceTagView {
|
|
62
|
+
tag: string;
|
|
63
|
+
tagged: string | null;
|
|
64
|
+
/** The runner whose statement the tag carries, or null for a tag that counts unsigned (#4031). */
|
|
65
|
+
signer: string | null;
|
|
66
|
+
source: TraceSource;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A commit's ci tags as readers count them (#4031, ws-103). */
|
|
70
|
+
export interface TraceTag {
|
|
71
|
+
/** `revoked` when a revoked tag counts, else `green` when a green tag counts, else `none`. */
|
|
72
|
+
state: "green" | "revoked" | "none";
|
|
73
|
+
green: TraceTagView | null;
|
|
74
|
+
revoked: (TraceTagView & Pick<RevokedTagView, "phase" | "run">) | null;
|
|
75
|
+
/** Tags on the commit that exist and don't count. */
|
|
76
|
+
ignored: IgnoredCiTag[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** One gate resolution: who let it through, on which channel, when, and for which plan. */
|
|
80
|
+
export interface TraceGate {
|
|
81
|
+
op: string;
|
|
82
|
+
gate: string;
|
|
83
|
+
environment: string | null;
|
|
84
|
+
resolvedBy: string;
|
|
85
|
+
relayedBy: string | null;
|
|
86
|
+
/** `cli`, `mcp` or `acp`, the channel the resolution was authored on (#2384), or null when the record doesn't say. */
|
|
87
|
+
channel: string | null;
|
|
88
|
+
at: string;
|
|
89
|
+
planDigest: string | null;
|
|
90
|
+
/** `planDigest`, when a change set is stored under it (#4022): `chant change-set show <changeSet>` prints it. */
|
|
91
|
+
changeSet: string | null;
|
|
92
|
+
changeSetSource: TraceSource | null;
|
|
93
|
+
/** A pass over an empty change set, written by the promote itself (#4045). */
|
|
94
|
+
empty: boolean;
|
|
95
|
+
/** Whether the approval carries an ssh seal (`chant approve --sign`, #3163). `status` judges it against the signers at base. */
|
|
96
|
+
sealed: boolean;
|
|
97
|
+
url: string | null;
|
|
98
|
+
source: TraceSource;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A release that shipped a commit of the walk. */
|
|
102
|
+
export interface TraceRelease {
|
|
103
|
+
releaseDigest: string;
|
|
104
|
+
label: string | null;
|
|
105
|
+
member: string | null;
|
|
106
|
+
/** The green commit it was cut from. */
|
|
107
|
+
commit: string;
|
|
108
|
+
/** That commit's ci tags. */
|
|
109
|
+
tag: TraceTag;
|
|
110
|
+
source: TraceSource;
|
|
111
|
+
cuts: { label: string | null; actor: string; timestamp: string; signed: boolean; runOrigin: RunOrigin | null; source: TraceSource }[];
|
|
112
|
+
/** The release gate's resolutions for it, oldest first. */
|
|
113
|
+
gates: TraceGate[];
|
|
114
|
+
/** The ids of its deploy records in `deploys`, oldest first. */
|
|
115
|
+
deploys: string[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A deploy record (`ReleaseRecord` in `<env>/releases.jsonl`). */
|
|
119
|
+
export interface TraceDeploy {
|
|
120
|
+
/** `deploy:<member>/<env>/<component>/<runId>@<timestamp>`, with `.` for a ledger of no member. */
|
|
121
|
+
id: string;
|
|
122
|
+
member: string | null;
|
|
123
|
+
env: string;
|
|
124
|
+
component: string;
|
|
125
|
+
timestamp: string;
|
|
126
|
+
actor: string;
|
|
127
|
+
approver: string | null;
|
|
128
|
+
runId: string;
|
|
129
|
+
runOrigin: RunOrigin | null;
|
|
130
|
+
gitSha: string;
|
|
131
|
+
digest: string;
|
|
132
|
+
/** The release it was made from, by its `releaseDigest` or through the release gate resolution its admission names (#4043), or null. */
|
|
133
|
+
release: string | null;
|
|
134
|
+
releaseSource: TraceSource | null;
|
|
135
|
+
/** The gate resolution its `admission` names (#4020), or null for an ungated deploy, one recorded before 0.124.28, or one whose resolution is not on the branch. */
|
|
136
|
+
admittedBy: TraceGate | null;
|
|
137
|
+
/** The release plan its `digest` names in `_plans/` (ws-055), or null when none is stored. */
|
|
138
|
+
plan: { document: ReleasePlan; source: TraceSource } | null;
|
|
139
|
+
source: TraceSource;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface TraceCommit {
|
|
143
|
+
/** The commit node. */
|
|
144
|
+
commit: string;
|
|
145
|
+
sha: string;
|
|
146
|
+
/**
|
|
147
|
+
* The work items the commit names, judged as `check --changes` judges it
|
|
148
|
+
* (#4029): `linked`, `unlinked`, `wrong` or `merge`. Null when no work kind
|
|
149
|
+
* was read.
|
|
150
|
+
*/
|
|
151
|
+
work: { state: CommitLinkState; items: TraceWork[]; problems: CommitLinkProblem[] } | null;
|
|
152
|
+
/** The work items whose window holds the commit (the `worked` within edges). They don't link it. */
|
|
153
|
+
windowMatches: TraceWork[];
|
|
154
|
+
tag: TraceTag;
|
|
155
|
+
/** The releases cut from the commit, or when none, the first release cut from a commit that has it in its history. */
|
|
156
|
+
releases: { release: string; link: "cut-from" | "contains"; source: TraceSource }[];
|
|
157
|
+
/** Per environment, the first deploy record built from the commit or a commit that has it in its history. */
|
|
158
|
+
deploys: { deploy: string; env: string; link: "built-from" | "contains"; source: TraceSource }[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export type TraceReasonCode = "ledger-unreadable" | "declaration-invalid";
|
|
162
|
+
|
|
163
|
+
/** The why chain (#4023): commits in walk order, then the releases and deploys they name. */
|
|
164
|
+
export interface IntentTrace {
|
|
165
|
+
commits: TraceCommit[];
|
|
166
|
+
releases: TraceRelease[];
|
|
167
|
+
deploys: TraceDeploy[];
|
|
168
|
+
/** Parts of the chain that could not be read: the ci tags (`declaration-invalid`) or a ledger (`ledger-unreadable`). The rest stands. */
|
|
169
|
+
reasons: { code: TraceReasonCode; message: string }[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface TraceInput {
|
|
173
|
+
top: string;
|
|
174
|
+
/** The commits of the walk, in its order. */
|
|
175
|
+
commits: { id: string; sha: string; trailers: Record<string, string[]>; joins: CommitTrailerJoins }[];
|
|
176
|
+
/** The work records the walk read, by work kind, and their nodes by `<kind>:<id>`. Null when no work kind was read. */
|
|
177
|
+
work: { index: Omit<WorkIndex, "ledger">; nodes: Map<string, string> } | null;
|
|
178
|
+
/** The work items whose window holds each commit, by sha. */
|
|
179
|
+
windows: Map<string, { id: string; node: string; path: string }[]>;
|
|
180
|
+
/**
|
|
181
|
+
* The ledgers to read, each as the directory `chant/lifecycle` stores
|
|
182
|
+
* resolve it from. `within` is a path part every ledger of the member has,
|
|
183
|
+
* `_members/<name>/`, so a member with nothing on the branch is skipped
|
|
184
|
+
* unread; null when it can't be told, as for a member at the root.
|
|
185
|
+
*/
|
|
186
|
+
ledgers: { member: string | null; cwd: string; within: string | null }[];
|
|
187
|
+
green: CiGreen | null;
|
|
188
|
+
/** Whether `later` is `sha` or has it as an ancestor, in the history read. */
|
|
189
|
+
contains: (sha: string, later: string) => boolean;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function tryGit(top: string, args: string[]): string | undefined {
|
|
193
|
+
try {
|
|
194
|
+
return execFileSync("git", args, { cwd: top, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 256 * 1024 * 1024 });
|
|
195
|
+
} catch {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const fileOf = (digest: string): string => `${digest.replace(/:/g, "_")}.json`;
|
|
201
|
+
const onBranch = (path: string): string => `chant/lifecycle:${path}`;
|
|
202
|
+
const byTime = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);
|
|
203
|
+
|
|
204
|
+
type RevokedView = (sha: string, r: CountedCiTag) => RevokedTagView;
|
|
205
|
+
|
|
206
|
+
function tagOf(sha: string, tags: CiTags | null, revokedView: RevokedView): TraceTag {
|
|
207
|
+
const green = tags?.green.get(sha);
|
|
208
|
+
const revoked = tags?.revoked.get(sha);
|
|
209
|
+
const view = (kind: "green" | "revoked", t: CountedCiTag): TraceTagView => ({
|
|
210
|
+
tag: `ci/${kind}/${sha}`,
|
|
211
|
+
tagged: t.tagged,
|
|
212
|
+
signer: t.signer,
|
|
213
|
+
source: { ref: `refs/tags/ci/${kind}/${sha}`, field: "name", value: sha },
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
state: revoked ? "revoked" : green ? "green" : "none",
|
|
217
|
+
green: green ? view("green", green) : null,
|
|
218
|
+
revoked: revoked ? { ...view("revoked", revoked), phase: revokedView(sha, revoked).phase, run: revokedView(sha, revoked).run } : null,
|
|
219
|
+
ignored: tags?.ignored.get(sha) ?? [],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Build the chain. Never throws for a ledger or tag it can't read: that is a reason. */
|
|
224
|
+
export async function buildTrace(input: TraceInput): Promise<IntentTrace> {
|
|
225
|
+
const { top } = input;
|
|
226
|
+
const reasons: IntentTrace["reasons"] = [];
|
|
227
|
+
const shas = input.commits.map((c) => c.sha);
|
|
228
|
+
|
|
229
|
+
// The ci tags, judged as release cut judges them.
|
|
230
|
+
let tags: CiTags | null = null;
|
|
231
|
+
const { revokedTagView } = await import("./ci-green");
|
|
232
|
+
try {
|
|
233
|
+
const { judgedCiTags } = await import("../lifecycle/release-revocation");
|
|
234
|
+
tags = await judgedCiTags(top, input.green);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
reasons.push({ code: "declaration-invalid", message: `the ci tags could not be judged: ${err instanceof Error ? err.message : String(err)}` });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// The work item each commit names (#4029).
|
|
240
|
+
const { judgeCommitLink, hasLeaseLedger, mergeCommits } = await import("./commit-links");
|
|
241
|
+
const index: WorkIndex | null = input.work ? { ...input.work.index, ledger: hasLeaseLedger(top) } : null;
|
|
242
|
+
const merges = index ? mergeCommits(top, shas) : new Set<string>();
|
|
243
|
+
const workNode = (id: string) => input.work?.nodes.get(id) ?? null;
|
|
244
|
+
|
|
245
|
+
// The ledgers on the local chant/lifecycle branch.
|
|
246
|
+
const branch = tryGit(top, ["rev-parse", "--verify", "--quiet", "refs/heads/chant/lifecycle"]) !== undefined;
|
|
247
|
+
interface Ledger {
|
|
248
|
+
member: string | null;
|
|
249
|
+
loc: { cwd: string };
|
|
250
|
+
prefix: string;
|
|
251
|
+
releases: Release[];
|
|
252
|
+
deploys: { env: string; record: ReleaseRecord }[];
|
|
253
|
+
}
|
|
254
|
+
const ledgers: Ledger[] = [];
|
|
255
|
+
// One listing of the branch, so a ledger that holds no release and no deploy costs nothing more.
|
|
256
|
+
const paths = branch ? (tryGit(top, ["ls-tree", "-r", "--name-only", "--full-tree", "refs/heads/chant/lifecycle"]) ?? "").split("\n").filter(Boolean) : [];
|
|
257
|
+
const holds = (prefix: string) =>
|
|
258
|
+
paths.some((p) => p.startsWith(prefix) && (p.startsWith(`${prefix}_releases/`) || /^[^/_][^/]*\/releases\.jsonl$/.test(p.slice(prefix.length))));
|
|
259
|
+
if (branch) {
|
|
260
|
+
const { resolveMemberLedger } = await import("../lifecycle/member-ledger");
|
|
261
|
+
const { readAllReleases } = await import("../lifecycle/release-store");
|
|
262
|
+
const { listReleaseEnvironments, readReleaseLedger } = await import("../lifecycle/release-ledger");
|
|
263
|
+
for (const l of input.ledgers) {
|
|
264
|
+
if (l.within !== null && !paths.some((p) => p.includes(l.within!))) continue;
|
|
265
|
+
const loc = { cwd: l.cwd };
|
|
266
|
+
try {
|
|
267
|
+
const prefix = (await resolveMemberLedger(l.cwd)).prefix;
|
|
268
|
+
if (ledgers.some((x) => x.prefix === prefix) || !holds(prefix)) continue;
|
|
269
|
+
const { releases } = await readAllReleases(loc);
|
|
270
|
+
const deploys: Ledger["deploys"] = [];
|
|
271
|
+
for (const env of await listReleaseEnvironments(loc)) {
|
|
272
|
+
for (const record of (await readReleaseLedger(env, loc)).records) deploys.push({ env, record });
|
|
273
|
+
}
|
|
274
|
+
ledgers.push({ member: l.member, loc, prefix, releases, deploys });
|
|
275
|
+
} catch (err) {
|
|
276
|
+
reasons.push({ code: "ledger-unreadable", message: `${l.member ?? "the workspace"}'s ledger on chant/lifecycle: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Each commit to the releases and deploys that shipped it.
|
|
282
|
+
const firstCut = new Map<string, string>();
|
|
283
|
+
const releaseHits = new Map<Ledger, Set<string>>();
|
|
284
|
+
const deployHits = new Map<string, { ledger: Ledger; env: string; record: ReleaseRecord }>();
|
|
285
|
+
const deployId = (l: Ledger, env: string, r: ReleaseRecord) => `deploy:${l.member ?? "."}/${env}/${r.component}/${r.runId}@${r.timestamp}`;
|
|
286
|
+
const perCommit = new Map<string, { releases: TraceCommit["releases"]; deploys: TraceCommit["deploys"] }>();
|
|
287
|
+
if (ledgers.some((l) => l.releases.length > 0)) {
|
|
288
|
+
const { readCuts } = await import("../lifecycle/release-store");
|
|
289
|
+
for (const l of ledgers) {
|
|
290
|
+
if (l.releases.length === 0) continue;
|
|
291
|
+
try {
|
|
292
|
+
for (const c of (await readCuts(l.loc)).cuts) if (!firstCut.has(c.releaseDigest) || byTime(c.timestamp, firstCut.get(c.releaseDigest)!) < 0) firstCut.set(c.releaseDigest, c.timestamp);
|
|
293
|
+
} catch {
|
|
294
|
+
// A release with no readable cut sorts last; its file still joins.
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
for (const sha of shas) {
|
|
299
|
+
const releases: TraceCommit["releases"] = [];
|
|
300
|
+
const deploys: TraceCommit["deploys"] = [];
|
|
301
|
+
for (const l of ledgers) {
|
|
302
|
+
const releaseSource = (r: Release): TraceSource => ({ ref: onBranch(`${l.prefix}_releases/${fileOf(r.releaseDigest)}`), field: "commit", value: r.commit });
|
|
303
|
+
const cut = l.releases.filter((r) => r.commit === sha);
|
|
304
|
+
const chosen: { r: Release; link: "cut-from" | "contains" }[] = cut.map((r) => ({ r, link: "cut-from" as const }));
|
|
305
|
+
if (chosen.length === 0) {
|
|
306
|
+
const later = l.releases.filter((r) => input.contains(sha, r.commit)).sort((a, b) => byTime(firstCut.get(a.releaseDigest) ?? "", firstCut.get(b.releaseDigest) ?? "") || byTime(a.releaseDigest, b.releaseDigest));
|
|
307
|
+
if (later.length > 0) chosen.push({ r: later[0], link: "contains" });
|
|
308
|
+
}
|
|
309
|
+
for (const { r, link } of chosen) {
|
|
310
|
+
releases.push({ release: r.releaseDigest, link, source: releaseSource(r) });
|
|
311
|
+
(releaseHits.get(l) ?? releaseHits.set(l, new Set()).get(l)!).add(r.releaseDigest);
|
|
312
|
+
}
|
|
313
|
+
const first = new Map<string, { env: string; record: ReleaseRecord; link: "built-from" | "contains" }>();
|
|
314
|
+
for (const d of l.deploys) {
|
|
315
|
+
const link = d.record.gitSha === sha ? "built-from" : input.contains(sha, d.record.gitSha) ? "contains" : null;
|
|
316
|
+
if (!link) continue;
|
|
317
|
+
const had = first.get(d.env);
|
|
318
|
+
if (!had || byTime(d.record.timestamp, had.record.timestamp) < 0) first.set(d.env, { ...d, link });
|
|
319
|
+
}
|
|
320
|
+
for (const [env, d] of [...first].sort(([a], [b]) => byTime(a, b))) {
|
|
321
|
+
const id = deployId(l, env, d.record);
|
|
322
|
+
deployHits.set(id, { ledger: l, env, record: d.record });
|
|
323
|
+
deploys.push({ deploy: id, env, link: d.link, source: { ref: onBranch(`${l.prefix}${env}/releases.jsonl`), field: "gitSha", value: d.record.gitSha } });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
perCommit.set(sha, { releases, deploys });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// The releases, through joinRelease, with their gates and deploys.
|
|
330
|
+
const { joinRelease } = await import("../lifecycle/release-join");
|
|
331
|
+
const { readGateLedger } = await import("../lifecycle/gate-ledger");
|
|
332
|
+
const { readStoredChangeSet, changeSetFile, CHANGE_SETS_DIR } = await import("../lifecycle/change-set-store");
|
|
333
|
+
const gateLedgers = new Map<string, Promise<GateResolutionRecord[]>>();
|
|
334
|
+
const resolutionsOf = (l: Ledger, op: string) => {
|
|
335
|
+
const key = `${l.prefix}\0${op}`;
|
|
336
|
+
let hit = gateLedgers.get(key);
|
|
337
|
+
if (!hit) {
|
|
338
|
+
hit = readGateLedger(op, l.loc).then(
|
|
339
|
+
(g) => g.resolutions,
|
|
340
|
+
() => [],
|
|
341
|
+
);
|
|
342
|
+
gateLedgers.set(key, hit);
|
|
343
|
+
}
|
|
344
|
+
return hit;
|
|
345
|
+
};
|
|
346
|
+
const changeSets = new Map<string, Promise<boolean>>();
|
|
347
|
+
const gateOf = async (l: Ledger, r: GateResolutionRecord): Promise<TraceGate> => {
|
|
348
|
+
let stored: Promise<boolean> | undefined;
|
|
349
|
+
if (r.planDigest) {
|
|
350
|
+
const key = `${l.prefix}\0${r.planDigest}`;
|
|
351
|
+
stored = changeSets.get(key);
|
|
352
|
+
if (!stored) {
|
|
353
|
+
stored = readStoredChangeSet(r.planDigest, l.loc).then(
|
|
354
|
+
(x) => x.status === "found",
|
|
355
|
+
() => false,
|
|
356
|
+
);
|
|
357
|
+
changeSets.set(key, stored);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const has = stored ? await stored : false;
|
|
361
|
+
return {
|
|
362
|
+
op: r.op,
|
|
363
|
+
gate: r.gate,
|
|
364
|
+
environment: r.environment ?? null,
|
|
365
|
+
resolvedBy: r.resolvedBy,
|
|
366
|
+
relayedBy: r.relayedBy ?? null,
|
|
367
|
+
channel: r.origin ?? null,
|
|
368
|
+
at: r.timestamp,
|
|
369
|
+
planDigest: r.planDigest ?? null,
|
|
370
|
+
changeSet: has ? r.planDigest! : null,
|
|
371
|
+
changeSetSource: has ? { ref: onBranch(`${l.prefix}${CHANGE_SETS_DIR}/${changeSetFile(r.planDigest!)}`), field: "planDigest", value: r.planDigest! } : null,
|
|
372
|
+
empty: r.empty === true,
|
|
373
|
+
sealed: r.seal !== undefined,
|
|
374
|
+
url: r.url ?? null,
|
|
375
|
+
source: { ref: onBranch(`${l.prefix}_gates/${r.op}.jsonl`), field: r.releaseDigest ? "releaseDigest" : "gate", value: r.releaseDigest ?? r.gate },
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const releases: TraceRelease[] = [];
|
|
380
|
+
const joinedDeploys = new Map<string, { ledger: Ledger; env: string; record: ReleaseRecord; release: JoinedRelease }>();
|
|
381
|
+
for (const l of ledgers) {
|
|
382
|
+
const wanted = releaseHits.get(l);
|
|
383
|
+
if (!wanted || wanted.size === 0) continue;
|
|
384
|
+
const commits = [...new Set(l.releases.filter((r) => wanted.has(r.releaseDigest)).map((r) => r.commit))];
|
|
385
|
+
let joined: JoinedRelease[];
|
|
386
|
+
try {
|
|
387
|
+
joined = (await joinRelease({ commits }, l.loc)).releases;
|
|
388
|
+
} catch (err) {
|
|
389
|
+
reasons.push({ code: "ledger-unreadable", message: `joining ${l.member ?? "the workspace"}'s releases: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` });
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
for (const j of joined) {
|
|
393
|
+
for (const d of j.deploys) joinedDeploys.set(deployId(l, d.env, d), { ledger: l, env: d.env, record: d, release: j });
|
|
394
|
+
releases.push({
|
|
395
|
+
releaseDigest: j.releaseDigest,
|
|
396
|
+
label: j.label,
|
|
397
|
+
member: j.release.member,
|
|
398
|
+
commit: j.commit,
|
|
399
|
+
tag: tagOf(j.commit, tags, revokedTagView),
|
|
400
|
+
source: { ref: onBranch(`${l.prefix}_releases/${fileOf(j.releaseDigest)}`), field: "releaseDigest", value: j.releaseDigest },
|
|
401
|
+
cuts: j.cuts.map((c: ReleaseCut) => ({
|
|
402
|
+
label: c.label,
|
|
403
|
+
actor: c.actor,
|
|
404
|
+
timestamp: c.timestamp,
|
|
405
|
+
signed: c.signature !== null,
|
|
406
|
+
runOrigin: c.runOrigin,
|
|
407
|
+
source: { ref: onBranch(`${l.prefix}_releases/cuts.jsonl`), field: "releaseDigest", value: c.releaseDigest },
|
|
408
|
+
})),
|
|
409
|
+
gates: await Promise.all([...j.gates.resolutions].sort((a, b) => byTime(a.timestamp, b.timestamp)).map((r) => gateOf(l, r))),
|
|
410
|
+
deploys: j.deploys.map((d) => deployId(l, d.env, d)),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// The deploys: those a commit names and those of each release, oldest first.
|
|
416
|
+
const { readReleasePlan } = await import("../lifecycle/plan-ledger");
|
|
417
|
+
const plans = new Map<string, Promise<ReleasePlan | null>>();
|
|
418
|
+
const all = new Map<string, { ledger: Ledger; env: string; record: ReleaseRecord; release?: JoinedRelease }>([...deployHits, ...joinedDeploys]);
|
|
419
|
+
const releaseResolutions = new Map<Ledger, GateResolutionRecord[]>();
|
|
420
|
+
const deploys: TraceDeploy[] = [];
|
|
421
|
+
for (const [id, d] of [...all].sort(([, a], [, b]) => byTime(a.record.timestamp, b.record.timestamp) || byTime(a.env, b.env))) {
|
|
422
|
+
const r = d.record;
|
|
423
|
+
const l = d.ledger;
|
|
424
|
+
let release: string | null = r.releaseDigest ?? null;
|
|
425
|
+
let releaseSource: TraceSource | null = release ? { ref: onBranch(`${l.prefix}${d.env}/releases.jsonl`), field: "releaseDigest", value: release } : null;
|
|
426
|
+
if (!release && r.admission?.op === "release") {
|
|
427
|
+
if (!releaseResolutions.has(l)) releaseResolutions.set(l, await resolutionsOf(l, "release"));
|
|
428
|
+
const { releaseOfDeploy } = await import("../lifecycle/release-join");
|
|
429
|
+
release = releaseOfDeploy(r, releaseResolutions.get(l)!) ?? null;
|
|
430
|
+
if (release) releaseSource = { ref: onBranch(`${l.prefix}${d.env}/releases.jsonl`), field: "admission", value: `${r.admission.gate}@${r.admission.resolvedAt}` };
|
|
431
|
+
}
|
|
432
|
+
let admittedBy: TraceGate | null = null;
|
|
433
|
+
if (r.admission) {
|
|
434
|
+
const found = (await resolutionsOf(l, r.admission.op)).find((x) => x.gate === r.admission!.gate && x.timestamp === r.admission!.resolvedAt);
|
|
435
|
+
if (found) admittedBy = await gateOf(l, found);
|
|
436
|
+
}
|
|
437
|
+
const planKey = `${l.prefix}\0${r.digest}`;
|
|
438
|
+
let plan = plans.get(planKey);
|
|
439
|
+
if (!plan) {
|
|
440
|
+
plan = readReleasePlan(r.digest, l.loc).catch(() => null);
|
|
441
|
+
plans.set(planKey, plan);
|
|
442
|
+
}
|
|
443
|
+
const document = await plan;
|
|
444
|
+
deploys.push({
|
|
445
|
+
id,
|
|
446
|
+
member: l.member,
|
|
447
|
+
env: d.env,
|
|
448
|
+
component: r.component,
|
|
449
|
+
timestamp: r.timestamp,
|
|
450
|
+
actor: r.actor,
|
|
451
|
+
approver: r.approver ?? null,
|
|
452
|
+
runId: r.runId,
|
|
453
|
+
runOrigin: r.runOrigin ?? null,
|
|
454
|
+
gitSha: r.gitSha,
|
|
455
|
+
digest: r.digest,
|
|
456
|
+
release,
|
|
457
|
+
releaseSource,
|
|
458
|
+
admittedBy,
|
|
459
|
+
plan: document ? { document, source: { ref: onBranch(`${l.prefix}_plans/${fileOf(r.digest)}`), field: "digest", value: r.digest } } : null,
|
|
460
|
+
source: { ref: onBranch(`${l.prefix}${d.env}/releases.jsonl`), field: "runId", value: r.runId },
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const commits: TraceCommit[] = input.commits.map((c) => {
|
|
465
|
+
let work: TraceCommit["work"] = null;
|
|
466
|
+
if (index) {
|
|
467
|
+
const judged = merges.has(c.sha) ? { state: "merge" as const, ids: [], problems: [] } : judgeCommitLink(c.trailers, c.joins, index);
|
|
468
|
+
const items: TraceWork[] = judged.ids.map((id) => {
|
|
469
|
+
const named = c.joins.records.some((r) => `${r.kind}:${r.id}` === id);
|
|
470
|
+
return {
|
|
471
|
+
id,
|
|
472
|
+
node: workNode(id),
|
|
473
|
+
source: named ? { ref: c.sha, field: "Chant-Record", value: id } : { ref: c.sha, field: "Chant-Lease", value: c.joins.lease?.token ?? "" },
|
|
474
|
+
};
|
|
475
|
+
});
|
|
476
|
+
work = { state: judged.state, items, problems: judged.problems };
|
|
477
|
+
}
|
|
478
|
+
const windowMatches: TraceWork[] = (input.windows.get(c.sha) ?? []).map((w) => ({ id: w.id, node: w.node, source: { ref: w.path, field: "window", value: "worked" } }));
|
|
479
|
+
const hit = perCommit.get(c.sha) ?? { releases: [], deploys: [] };
|
|
480
|
+
return { commit: c.id, sha: c.sha, work, windowMatches, tag: tagOf(c.sha, tags, revokedTagView), releases: hit.releases, deploys: hit.deploys };
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
return { commits, releases, deploys, reasons };
|
|
484
|
+
}
|