@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,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test stand-ins for the publish targets (#4047): an OCI registry fake that
|
|
3
|
+
* listens on 127.0.0.1 inside the test process and answers with a Bearer
|
|
4
|
+
* challenge as GHCR and Docker Hub do, an OCI image layout writer, a fake
|
|
5
|
+
* `npm` that records each call and keeps a registry with dist-tags in a file,
|
|
6
|
+
* and a forge fake with draft releases (#4048). Nothing here reaches a network or a real
|
|
7
|
+
* registry, and every server a test starts is closed by it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
13
|
+
import type { AddressInfo } from "node:net";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
|
|
16
|
+
const sha = (b: Uint8Array | string) => `sha256:${createHash("sha256").update(b).digest("hex")}`;
|
|
17
|
+
|
|
18
|
+
export interface FakeRegistry {
|
|
19
|
+
/** `127.0.0.1:<port>`, for a repository reference. */
|
|
20
|
+
host: string;
|
|
21
|
+
blobs: Map<string, Buffer>;
|
|
22
|
+
manifests: Map<string, { name: string; type: string; bytes: Buffer }>;
|
|
23
|
+
/** Each request: method, path and the authorization header it carried. */
|
|
24
|
+
requests: Array<{ method: string; path: string; authorization: string | null }>;
|
|
25
|
+
uploads: number;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A registry fake with a Bearer token service that accepts `username` and `password`. */
|
|
30
|
+
export async function startFakeRegistry(creds: { username: string; password: string; token: string }): Promise<FakeRegistry> {
|
|
31
|
+
const blobs = new Map<string, Buffer>();
|
|
32
|
+
const manifests = new Map<string, { name: string; type: string; bytes: Buffer }>();
|
|
33
|
+
const requests: FakeRegistry["requests"] = [];
|
|
34
|
+
const state = { uploads: 0, port: 0 };
|
|
35
|
+
const read = (req: IncomingMessage) =>
|
|
36
|
+
new Promise<Buffer>((res) => {
|
|
37
|
+
const parts: Buffer[] = [];
|
|
38
|
+
req.on("data", (c: Buffer) => parts.push(c));
|
|
39
|
+
req.on("end", () => res(Buffer.concat(parts)));
|
|
40
|
+
});
|
|
41
|
+
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
42
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${state.port}`);
|
|
43
|
+
const body = await read(req);
|
|
44
|
+
requests.push({ method: req.method ?? "", path: url.pathname, authorization: req.headers.authorization ?? null });
|
|
45
|
+
const send = (status: number, headers: Record<string, string> = {}, text = "") => {
|
|
46
|
+
res.writeHead(status, headers);
|
|
47
|
+
res.end(text);
|
|
48
|
+
};
|
|
49
|
+
if (url.pathname === "/token") {
|
|
50
|
+
const basic = `Basic ${Buffer.from(`${creds.username}:${creds.password}`).toString("base64")}`;
|
|
51
|
+
if (req.headers.authorization !== basic) return send(401, {}, "bad credentials");
|
|
52
|
+
return send(200, { "content-type": "application/json" }, JSON.stringify({ token: creds.token }));
|
|
53
|
+
}
|
|
54
|
+
if (req.headers.authorization !== `Bearer ${creds.token}`) {
|
|
55
|
+
return send(401, { "www-authenticate": `Bearer realm="http://127.0.0.1:${state.port}/token",service="fake-registry"` }, "unauthorized");
|
|
56
|
+
}
|
|
57
|
+
let m = url.pathname.match(/^\/v2\/(.+)\/blobs\/uploads\/(.*)$/);
|
|
58
|
+
if (m) {
|
|
59
|
+
if (req.method === "POST") return send(202, { location: `/v2/${m[1]}/blobs/uploads/u${requests.length}?_state=s` });
|
|
60
|
+
const digest = url.searchParams.get("digest") ?? "";
|
|
61
|
+
if (req.method === "PUT" && sha(body) === digest && url.searchParams.get("_state") === "s") {
|
|
62
|
+
blobs.set(digest, body);
|
|
63
|
+
state.uploads++;
|
|
64
|
+
return send(201, { "docker-content-digest": digest });
|
|
65
|
+
}
|
|
66
|
+
return send(400, {}, "digest invalid");
|
|
67
|
+
}
|
|
68
|
+
m = url.pathname.match(/^\/v2\/(.+)\/blobs\/(sha256:[0-9a-f]+)$/);
|
|
69
|
+
if (m && req.method === "HEAD") return send(blobs.has(m[2]!) ? 200 : 404);
|
|
70
|
+
m = url.pathname.match(/^\/v2\/(.+)\/manifests\/(.+)$/);
|
|
71
|
+
if (m && (req.method === "HEAD" || req.method === "GET")) {
|
|
72
|
+
const found = manifests.get(m[2]!);
|
|
73
|
+
return found ? send(200, { "content-type": found.type, "docker-content-digest": sha(found.bytes) }, req.method === "GET" ? found.bytes.toString("utf-8") : "") : send(404);
|
|
74
|
+
}
|
|
75
|
+
if (m && req.method === "PUT") {
|
|
76
|
+
const ref = m[2]!;
|
|
77
|
+
if (ref.startsWith("sha256:") && sha(body) !== ref) return send(400, {}, "manifest digest invalid");
|
|
78
|
+
const parsed = JSON.parse(body.toString("utf-8")) as { config?: { digest: string }; layers?: Array<{ digest: string }> };
|
|
79
|
+
for (const d of [parsed.config, ...(parsed.layers ?? [])]) if (d && !blobs.has(d.digest)) return send(400, {}, `blob unknown ${d.digest}`);
|
|
80
|
+
manifests.set(ref, { name: m[1]!, type: String(req.headers["content-type"]), bytes: body });
|
|
81
|
+
return send(201, { "docker-content-digest": sha(body) });
|
|
82
|
+
}
|
|
83
|
+
return send(404, {}, "not found");
|
|
84
|
+
});
|
|
85
|
+
await new Promise<void>((r) => server.listen(0, "127.0.0.1", () => r()));
|
|
86
|
+
state.port = (server.address() as AddressInfo).port;
|
|
87
|
+
return {
|
|
88
|
+
host: `127.0.0.1:${state.port}`,
|
|
89
|
+
blobs,
|
|
90
|
+
manifests,
|
|
91
|
+
requests,
|
|
92
|
+
get uploads() {
|
|
93
|
+
return state.uploads;
|
|
94
|
+
},
|
|
95
|
+
close: () => new Promise<void>((r) => server.close(() => r())),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Write a one-image OCI layout at `dir`, returning the manifest's and the config's digests. */
|
|
100
|
+
export function writeOciLayout(dir: string, content = "layer bytes"): { digest: string; configDigest: string } {
|
|
101
|
+
const blobsDir = join(dir, "blobs", "sha256");
|
|
102
|
+
mkdirSync(blobsDir, { recursive: true });
|
|
103
|
+
const put = (bytes: string) => {
|
|
104
|
+
const d = sha(bytes);
|
|
105
|
+
writeFileSync(join(blobsDir, d.slice(7)), bytes);
|
|
106
|
+
return { digest: d, size: Buffer.byteLength(bytes) };
|
|
107
|
+
};
|
|
108
|
+
const layer = put(content);
|
|
109
|
+
const config = put(JSON.stringify({ architecture: "amd64", os: "linux", rootfs: { type: "layers", diff_ids: [layer.digest] } }));
|
|
110
|
+
const manifest = put(
|
|
111
|
+
JSON.stringify({
|
|
112
|
+
schemaVersion: 2,
|
|
113
|
+
mediaType: "application/vnd.oci.image.manifest.v1+json",
|
|
114
|
+
config: { mediaType: "application/vnd.oci.image.config.v1+json", ...config },
|
|
115
|
+
layers: [{ mediaType: "application/vnd.oci.image.layer.v1.tar", ...layer }],
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
writeFileSync(join(dir, "oci-layout"), JSON.stringify({ imageLayoutVersion: "1.0.0" }));
|
|
119
|
+
writeFileSync(join(dir, "index.json"), JSON.stringify({ schemaVersion: 2, manifests: [{ mediaType: "application/vnd.oci.image.manifest.v1+json", ...manifest }] }));
|
|
120
|
+
return { digest: manifest.digest, configDigest: config.digest };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** What the fake npm's registry holds: each package's versions and dist-tags. */
|
|
124
|
+
export interface FakeNpmRegistry {
|
|
125
|
+
packages: Record<string, { versions: string[]; distTags: Record<string, string> }>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The registry file a fake npm writing calls to `log` keeps its state in. */
|
|
129
|
+
export const fakeNpmRegistryFile = (log: string) => `${log}.registry.json`;
|
|
130
|
+
|
|
131
|
+
/** Read the fake npm's registry. */
|
|
132
|
+
export function readFakeNpmRegistry(log: string): FakeNpmRegistry {
|
|
133
|
+
const file = fakeNpmRegistryFile(log);
|
|
134
|
+
return existsSync(file) ? (JSON.parse(readFileSync(file, "utf-8")) as FakeNpmRegistry) : { packages: {} };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Seed the fake npm's registry, such as with an earlier version as `latest`. */
|
|
138
|
+
export function seedFakeNpmRegistry(log: string, registry: FakeNpmRegistry): void {
|
|
139
|
+
writeFileSync(fakeNpmRegistryFile(log), JSON.stringify(registry));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Write a fake `npm` at `<binDir>/npm` that appends `{ args, tokenSet }` to
|
|
144
|
+
* `log` for each call and prints the token it was given (so a test can show
|
|
145
|
+
* it is masked). It keeps a registry in `<log>.registry.json`, as npm's does:
|
|
146
|
+
*
|
|
147
|
+
* - `publish <dir|tgz> [--tag <t>]` adds the version under `<t>` (default
|
|
148
|
+
* `latest`), and refuses a version already there with a 403. As npm's
|
|
149
|
+
* registry does, a package's first version is also `latest`. It exits 1
|
|
150
|
+
* when the package path contains `$FAKE_NPM_FAIL`.
|
|
151
|
+
* - `view <name>@<version> version` prints the version when it is there.
|
|
152
|
+
* - `dist-tag add <name>@<version> <tag>` points the tag at a version that is
|
|
153
|
+
* there. It exits 1 when the name contains `$FAKE_NPM_FAIL_TAG`.
|
|
154
|
+
* - `install` (#4049) checks that every dependency in `./package.json` is in
|
|
155
|
+
* its registry at that version, and installs nothing.
|
|
156
|
+
* - `audit signatures` (#4049) prints what npm prints for verified
|
|
157
|
+
* signatures and attestations, and exits 1 when `$FAKE_NPM_AUDIT_FAIL` is set.
|
|
158
|
+
*/
|
|
159
|
+
export function writeFakeNpm(binDir: string, log: string): string {
|
|
160
|
+
mkdirSync(binDir, { recursive: true });
|
|
161
|
+
const file = join(binDir, "npm");
|
|
162
|
+
writeFileSync(
|
|
163
|
+
file,
|
|
164
|
+
`#!${process.execPath}
|
|
165
|
+
const fs = require("fs");
|
|
166
|
+
const path = require("path");
|
|
167
|
+
const { execFileSync } = require("child_process");
|
|
168
|
+
const args = process.argv.slice(2);
|
|
169
|
+
const token = process.env.NODE_AUTH_TOKEN || "";
|
|
170
|
+
const regFile = ${JSON.stringify(log)} + ".registry.json";
|
|
171
|
+
fs.appendFileSync(${JSON.stringify(log)}, JSON.stringify({ args, tokenSet: token !== "" }) + "\\n");
|
|
172
|
+
console.log("npm notice using //registry.example/:_authToken=" + token);
|
|
173
|
+
console.error("npm notice token " + token);
|
|
174
|
+
const reg = fs.existsSync(regFile) ? JSON.parse(fs.readFileSync(regFile, "utf-8")) : { packages: {} };
|
|
175
|
+
const save = () => fs.writeFileSync(regFile, JSON.stringify(reg));
|
|
176
|
+
const flag = (name) => { const i = args.indexOf(name); return i < 0 ? null : args[i + 1]; };
|
|
177
|
+
const split = (spec) => { const at = spec.lastIndexOf("@"); return [spec.slice(0, at), spec.slice(at + 1)]; };
|
|
178
|
+
if (args[0] === "publish") {
|
|
179
|
+
if (process.env.FAKE_NPM_FAIL && args[1] && args[1].includes(process.env.FAKE_NPM_FAIL)) { console.error("npm error 403 Forbidden"); process.exit(1); }
|
|
180
|
+
const text = fs.statSync(args[1]).isDirectory() ? fs.readFileSync(path.join(args[1], "package.json"), "utf-8") : execFileSync("tar", ["-xzOf", args[1], "package/package.json"], { encoding: "utf-8" });
|
|
181
|
+
const { name, version } = JSON.parse(text);
|
|
182
|
+
const p = reg.packages[name] || (reg.packages[name] = { versions: [], distTags: {} });
|
|
183
|
+
if (p.versions.includes(version)) { console.error("npm error 403 You cannot publish over the previously published versions: " + version); process.exit(1); }
|
|
184
|
+
p.versions.push(version);
|
|
185
|
+
p.distTags[flag("--tag") || "latest"] = version;
|
|
186
|
+
if (!p.distTags.latest) p.distTags.latest = version;
|
|
187
|
+
save();
|
|
188
|
+
console.log("+ " + name + "@" + version);
|
|
189
|
+
} else if (args[0] === "view") {
|
|
190
|
+
const [name, version] = split(args[1]);
|
|
191
|
+
const p = reg.packages[name];
|
|
192
|
+
if (!p) { console.error("npm error 404 Not Found - " + name); process.exit(1); }
|
|
193
|
+
if (p.versions.includes(version)) console.log(version);
|
|
194
|
+
} else if (args[0] === "dist-tag" && args[1] === "add") {
|
|
195
|
+
const [name, version] = split(args[2]);
|
|
196
|
+
if (process.env.FAKE_NPM_FAIL_TAG && name.includes(process.env.FAKE_NPM_FAIL_TAG)) { console.error("npm error 403 Forbidden - no permission to change dist-tags"); process.exit(1); }
|
|
197
|
+
const p = reg.packages[name];
|
|
198
|
+
if (!p || !p.versions.includes(version)) { console.error("npm error 404 " + args[2] + " is not in the npm registry"); process.exit(1); }
|
|
199
|
+
p.distTags[args[3]] = version;
|
|
200
|
+
save();
|
|
201
|
+
console.log("+" + args[3] + ": " + args[2]);
|
|
202
|
+
} else if (args[0] === "install") {
|
|
203
|
+
const deps = JSON.parse(fs.readFileSync("package.json", "utf-8")).dependencies || {};
|
|
204
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
205
|
+
const p = reg.packages[name];
|
|
206
|
+
if (!p || !p.versions.includes(version)) { console.error("npm error 404 " + name + "@" + version + " is not in this registry"); process.exit(1); }
|
|
207
|
+
}
|
|
208
|
+
console.log("added " + Object.keys(deps).length + " packages");
|
|
209
|
+
} else if (args[0] === "audit" && args[1] === "signatures") {
|
|
210
|
+
const n = Object.keys(JSON.parse(fs.readFileSync("package.json", "utf-8")).dependencies || {}).length;
|
|
211
|
+
if (process.env.FAKE_NPM_AUDIT_FAIL) { console.error(n + " packages have invalid attestations"); process.exit(1); }
|
|
212
|
+
console.log("audited " + n + " packages");
|
|
213
|
+
console.log(n + " packages have verified registry signatures");
|
|
214
|
+
console.log(n + " packages have verified attestations");
|
|
215
|
+
} else {
|
|
216
|
+
console.error("fake npm: unknown command " + args.join(" ")); process.exit(2);
|
|
217
|
+
}
|
|
218
|
+
`,
|
|
219
|
+
);
|
|
220
|
+
chmodSync(file, 0o755);
|
|
221
|
+
return file;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** A forge release as {@link startFakeForge} keeps it. */
|
|
225
|
+
export interface FakeForgeRelease {
|
|
226
|
+
id: number;
|
|
227
|
+
tag_name: string;
|
|
228
|
+
draft: boolean;
|
|
229
|
+
target: string;
|
|
230
|
+
assets: Array<{ name: string; digest: string | null; bytes: string }>;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** A forge fake answering through `fetch`: GitHub or Forgejo with draft releases, or GitLab with releases, links and a generic package registry. */
|
|
234
|
+
export interface FakeForge {
|
|
235
|
+
fetch: (url: string, init: { method: string; headers: Record<string, string>; body?: Uint8Array | string }) => Promise<{ ok: boolean; status: number; headers: { get(name: string): string | null }; text(): Promise<string> }>;
|
|
236
|
+
calls: Array<{ method: string; url: string; headers: Record<string, string>; body?: Uint8Array | string }>;
|
|
237
|
+
releases: FakeForgeRelease[];
|
|
238
|
+
/** GitLab: the package registry's files, by URL. */
|
|
239
|
+
packages: Map<string, string>;
|
|
240
|
+
/** The releases a consumer sees: published (not draft) ones, with their asset names. */
|
|
241
|
+
visible(): Array<{ tag: string; assets: string[] }>;
|
|
242
|
+
/** Make the next request whose `METHOD url` starts with `prefix` answer 500. */
|
|
243
|
+
failOnce(prefix: string): void;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** A forge fake at `api` for `repo`, which holds `releases` to begin with. */
|
|
247
|
+
export function startFakeForge(forge: "github" | "forgejo" | "gitlab", api: string, repo: string, releases: FakeForgeRelease[] = []): FakeForge {
|
|
248
|
+
const calls: FakeForge["calls"] = [];
|
|
249
|
+
const packages = new Map<string, string>();
|
|
250
|
+
const failures: string[] = [];
|
|
251
|
+
let nextId = 100 + releases.length;
|
|
252
|
+
const root = forge === "gitlab" ? `${api}/projects/${encodeURIComponent(repo)}` : `${api}/repos/${repo}`;
|
|
253
|
+
const uploads = forge === "github" ? "https://uploads.forge.test" : api;
|
|
254
|
+
const json = (status: number, body: unknown) => ({ ok: status < 300, status, headers: { get: () => null }, text: async () => (body === undefined ? "" : JSON.stringify(body)) });
|
|
255
|
+
const view = (r: FakeForgeRelease) =>
|
|
256
|
+
forge === "gitlab"
|
|
257
|
+
? { tag_name: r.tag_name, assets: { links: r.assets.map((a) => ({ name: a.name })) } }
|
|
258
|
+
: { id: r.id, tag_name: r.tag_name, draft: r.draft, upload_url: `${uploads}/repos/${repo}/releases/${r.id}/assets{?name,label}`, assets: r.assets.map((a) => ({ name: a.name, ...(forge === "github" ? { digest: a.digest } : {}) })) };
|
|
259
|
+
const text = (b: Uint8Array | string | undefined) => (b === undefined ? "" : typeof b === "string" ? b : Buffer.from(b).toString("utf-8"));
|
|
260
|
+
const fetch: FakeForge["fetch"] = async (url, init) => {
|
|
261
|
+
calls.push({ method: init.method, url, headers: init.headers, ...(init.body !== undefined ? { body: init.body } : {}) });
|
|
262
|
+
const key = `${init.method} ${url}`;
|
|
263
|
+
const f = failures.findIndex((p) => key.startsWith(p));
|
|
264
|
+
if (f >= 0) {
|
|
265
|
+
failures.splice(f, 1);
|
|
266
|
+
return json(500, { message: "Internal Server Error" });
|
|
267
|
+
}
|
|
268
|
+
const u = new URL(url);
|
|
269
|
+
const path = url.split("?")[0]!;
|
|
270
|
+
if (forge === "gitlab") {
|
|
271
|
+
if (init.method === "PUT" && path.startsWith(`${root}/packages/generic/`)) {
|
|
272
|
+
packages.set(path, text(init.body));
|
|
273
|
+
return json(201, { message: "201 Created" });
|
|
274
|
+
}
|
|
275
|
+
const byTag = path.startsWith(`${root}/releases/`) ? decodeURIComponent(path.slice(`${root}/releases/`.length)) : null;
|
|
276
|
+
if (init.method === "GET" && byTag !== null) {
|
|
277
|
+
const r = releases.find((x) => x.tag_name === byTag);
|
|
278
|
+
return r ? json(200, view(r)) : json(404, { message: "404 Not Found" });
|
|
279
|
+
}
|
|
280
|
+
if (init.method === "POST" && path === `${root}/releases`) {
|
|
281
|
+
const b = JSON.parse(text(init.body)) as { tag_name: string; ref: string; assets?: { links?: Array<{ name: string; url: string }> } };
|
|
282
|
+
if (releases.some((x) => x.tag_name === b.tag_name)) return json(409, { message: "Release already exists" });
|
|
283
|
+
const r: FakeForgeRelease = { id: nextId++, tag_name: b.tag_name, draft: false, target: b.ref, assets: (b.assets?.links ?? []).map((l) => ({ name: l.name, digest: null, bytes: packages.get(l.url) ?? "" })) };
|
|
284
|
+
releases.push(r);
|
|
285
|
+
return json(201, view(r));
|
|
286
|
+
}
|
|
287
|
+
return json(404, { message: "404 Not Found" });
|
|
288
|
+
}
|
|
289
|
+
if (init.method === "GET" && path.startsWith(`${root}/releases/tags/`)) {
|
|
290
|
+
const tag = decodeURIComponent(path.slice(`${root}/releases/tags/`.length));
|
|
291
|
+
const r = releases.find((x) => x.tag_name === tag && !x.draft);
|
|
292
|
+
return r ? json(200, view(r)) : json(404, { message: "Not Found" });
|
|
293
|
+
}
|
|
294
|
+
if (init.method === "GET" && path === `${root}/releases`) return json(200, releases.map(view));
|
|
295
|
+
if (init.method === "POST" && path === `${root}/releases`) {
|
|
296
|
+
const b = JSON.parse(text(init.body)) as { tag_name: string; target_commitish: string; draft?: boolean };
|
|
297
|
+
const r: FakeForgeRelease = { id: nextId++, tag_name: b.tag_name, draft: b.draft === true, target: b.target_commitish, assets: [] };
|
|
298
|
+
releases.push(r);
|
|
299
|
+
return json(201, view(r));
|
|
300
|
+
}
|
|
301
|
+
const asset = path.match(/\/releases\/(\d+)\/assets$/);
|
|
302
|
+
if (init.method === "POST" && asset && (path.startsWith(uploads) || path.startsWith(root))) {
|
|
303
|
+
const r = releases.find((x) => x.id === Number(asset[1]));
|
|
304
|
+
const name = u.searchParams.get("name") ?? "";
|
|
305
|
+
if (!r) return json(404, { message: "Not Found" });
|
|
306
|
+
if (r.assets.some((a) => a.name === name)) return json(422, { message: "already_exists" });
|
|
307
|
+
let bytes = text(init.body);
|
|
308
|
+
if (forge === "forgejo") bytes = bytes.split("\r\n\r\n")[1]?.split("\r\n--")[0] ?? "";
|
|
309
|
+
r.assets.push({ name, digest: sha(bytes), bytes });
|
|
310
|
+
return json(201, { name, browser_download_url: `https://forge.test/${repo}/releases/download/${r.tag_name}/${name}` });
|
|
311
|
+
}
|
|
312
|
+
const edit = path.match(/\/releases\/(\d+)$/);
|
|
313
|
+
if (init.method === "PATCH" && edit) {
|
|
314
|
+
const r = releases.find((x) => x.id === Number(edit[1]));
|
|
315
|
+
if (!r) return json(404, { message: "Not Found" });
|
|
316
|
+
const b = JSON.parse(text(init.body)) as { draft?: boolean };
|
|
317
|
+
if (b.draft === false) r.draft = false;
|
|
318
|
+
return json(200, view(r));
|
|
319
|
+
}
|
|
320
|
+
return json(404, { message: "Not Found" });
|
|
321
|
+
};
|
|
322
|
+
return {
|
|
323
|
+
fetch,
|
|
324
|
+
calls,
|
|
325
|
+
releases,
|
|
326
|
+
packages,
|
|
327
|
+
visible: () => releases.filter((r) => !r.draft).map((r) => ({ tag: r.tag_name, assets: r.assets.map((a) => a.name) })),
|
|
328
|
+
failOnce: (prefix) => void failures.push(prefix),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** A small SPDX document, as `npm sbom --sbom-format spdx` writes one. */
|
|
333
|
+
export function spdxDocument(name: string): string {
|
|
334
|
+
return `${JSON.stringify({ spdxVersion: "SPDX-2.3", dataLicense: "CC0-1.0", SPDXID: "SPDXRef-DOCUMENT", name, documentNamespace: `https://example.test/${name}`, creationInfo: { created: "2026-10-17T00:00:00Z", creators: ["Tool: npm"] }, packages: [] }, null, 2)}\n`;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Write an npm package directory, with an `sbom.spdx.json` at its root unless `sbom` is false (#4049). */
|
|
338
|
+
export function writePackage(dir: string, name: string, version: string, sbom = true): void {
|
|
339
|
+
mkdirSync(dir, { recursive: true });
|
|
340
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({ name, version }, null, 2));
|
|
341
|
+
if (sbom) writeFileSync(join(dir, "sbom.spdx.json"), spdxDocument(name));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Write a `package-lock.json` at `dir`, which the OCI and forge targets write their SBOM from (#4049). */
|
|
345
|
+
export function writeLockfile(dir: string): void {
|
|
346
|
+
mkdirSync(dir, { recursive: true });
|
|
347
|
+
writeFileSync(
|
|
348
|
+
join(dir, "package-lock.json"),
|
|
349
|
+
JSON.stringify({ name: "acme", lockfileVersion: 3, packages: { "": { name: "acme" }, "node_modules/left-pad": { version: "1.3.0" } } }, null, 2),
|
|
350
|
+
);
|
|
351
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stored change sets (#4022) against temp git repos: the store, the gate's
|
|
3
|
+
* call that maps and stores, `chant change-set show`'s document and the link
|
|
4
|
+
* a gate in `workspace status` carries. Graph reads are injected here;
|
|
5
|
+
* `gate-change-set.e2e.test.ts` reads a real one.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, test } from "vitest";
|
|
9
|
+
import { withTestDir } from "@intentius/chant-test-utils";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { composeChangeSet, lifecyclePlanPart, type ChangeSetDocument } from "../change-set";
|
|
14
|
+
import type { ChangeSet as LifecycleChangeSet } from "./change-set";
|
|
15
|
+
import { CHANGE_SETS_DIR, changeSetFile, ChangeSetStoreError, persistChangeSet, readStoredChangeSet } from "./change-set-store";
|
|
16
|
+
import { storeGateChangeSet } from "./gate-change-set";
|
|
17
|
+
import { appendPendingGate } from "./gate-ledger";
|
|
18
|
+
import { writeLedgerFiles } from "./git";
|
|
19
|
+
import { clearMemberLedgerCache } from "./member-ledger";
|
|
20
|
+
import { changeSetShowDocument } from "../workspace/change-set-show";
|
|
21
|
+
import { readMemberGates } from "../workspace/status-gates";
|
|
22
|
+
import { loadAjv2020 } from "../workspace/ajv2020";
|
|
23
|
+
import showSchema from "../workspace/change-set-show.schema.json";
|
|
24
|
+
import changeSetSchema from "../workspace/change-set.schema.json";
|
|
25
|
+
|
|
26
|
+
const plan = JSON.parse(readFileSync(join(import.meta.dirname, "..", "__fixtures__", "change-set", "lifecycle-plan.floci.json"), "utf8")) as LifecycleChangeSet;
|
|
27
|
+
const doc = (): ChangeSetDocument => composeChangeSet([lifecyclePlanPart({ member: "queues", plan })]);
|
|
28
|
+
|
|
29
|
+
function git(dir: string, ...args: string[]): string {
|
|
30
|
+
const r = spawnSync("git", args, { cwd: dir, encoding: "utf-8" });
|
|
31
|
+
if (r.status !== 0) throw new Error(`git ${args.join(" ")}: ${r.stderr}`);
|
|
32
|
+
return r.stdout.trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function initRepo(dir: string): string {
|
|
36
|
+
git(dir, "init", "-q", "-b", "main");
|
|
37
|
+
git(dir, "config", "user.email", "test@chant.dev");
|
|
38
|
+
git(dir, "config", "user.name", "Test");
|
|
39
|
+
writeFileSync(join(dir, "README.md"), "fixture\n");
|
|
40
|
+
git(dir, "add", ".");
|
|
41
|
+
git(dir, "commit", "-q", "-m", "init");
|
|
42
|
+
return git(dir, "rev-parse", "HEAD");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function validator(schema: object) {
|
|
46
|
+
const Ajv = loadAjv2020();
|
|
47
|
+
const ajv = new Ajv({ strict: false, allErrors: true }) as unknown as { compile(s: object): ((d: unknown) => boolean) & { errors?: unknown } };
|
|
48
|
+
return ajv.compile(schema);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("persistChangeSet and readStoredChangeSet", () => {
|
|
52
|
+
test("stores a document under its digest once, and reads it back", async () => {
|
|
53
|
+
await withTestDir(async (dir) => {
|
|
54
|
+
initRepo(dir);
|
|
55
|
+
const d = doc();
|
|
56
|
+
expect(await readStoredChangeSet(d.digest, { cwd: dir })).toEqual({ status: "missing" });
|
|
57
|
+
const first = await persistChangeSet(d, { cwd: dir });
|
|
58
|
+
expect(first.written).toBe(true);
|
|
59
|
+
expect(git(dir, "ls-tree", "--name-only", `chant/lifecycle:${CHANGE_SETS_DIR}`)).toBe(changeSetFile(d.digest));
|
|
60
|
+
expect(changeSetFile(d.digest)).not.toContain(":");
|
|
61
|
+
const again = await persistChangeSet(d, { cwd: dir });
|
|
62
|
+
expect(again).toEqual({ commit: null, written: false });
|
|
63
|
+
const read = await readStoredChangeSet(d.digest, { cwd: dir });
|
|
64
|
+
expect(read).toEqual({ status: "found", document: JSON.parse(JSON.stringify(d)) });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("refuses a document that does not hash to its digest", async () => {
|
|
69
|
+
await withTestDir(async (dir) => {
|
|
70
|
+
initRepo(dir);
|
|
71
|
+
const d = doc();
|
|
72
|
+
const forged = { ...d, entries: d.entries.slice(1) };
|
|
73
|
+
await expect(persistChangeSet(forged, { cwd: dir })).rejects.toBeInstanceOf(ChangeSetStoreError);
|
|
74
|
+
await expect(persistChangeSet({ ...d, $schema: "x" } as never, { cwd: dir })).rejects.toBeInstanceOf(ChangeSetStoreError);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("a file under a digest that is not that document reads as malformed", async () => {
|
|
79
|
+
await withTestDir(async (dir) => {
|
|
80
|
+
initRepo(dir);
|
|
81
|
+
const d = doc();
|
|
82
|
+
const other = { ...d, entries: d.entries.slice(1) };
|
|
83
|
+
await writeLedgerFiles([{ path: `${CHANGE_SETS_DIR}/${changeSetFile(d.digest)}`, content: JSON.stringify(other), expectPriorSha: null }], "forged", { cwd: dir });
|
|
84
|
+
const read = await readStoredChangeSet(d.digest, { cwd: dir });
|
|
85
|
+
expect(read.status).toBe("malformed");
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("storeGateChangeSet", () => {
|
|
91
|
+
test("maps the entries onto the graph it reads, and stores in the member's ledger", async () => {
|
|
92
|
+
await withTestDir(async (dir) => {
|
|
93
|
+
initRepo(dir);
|
|
94
|
+
writeFileSync(join(dir, "chant.workspace.json"), JSON.stringify({ name: "acme", schema: 1, members: [{ name: "delivery", dir: "delivery", kind: "chant" }] }));
|
|
95
|
+
mkdirSync(join(dir, "delivery"));
|
|
96
|
+
clearMemberLedgerCache();
|
|
97
|
+
const d = doc();
|
|
98
|
+
const reads: string[] = [];
|
|
99
|
+
const stored = await storeGateChangeSet({
|
|
100
|
+
doc: d,
|
|
101
|
+
cwd: join(dir, "delivery"),
|
|
102
|
+
at: "c".repeat(40),
|
|
103
|
+
readGraph: async (at) => {
|
|
104
|
+
reads.push(at);
|
|
105
|
+
return { ids: ["jobs", "events", "dlq"] };
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
expect(stored).toEqual({ digest: d.digest, written: true, graph: { member: "delivery", at: "c".repeat(40) } });
|
|
109
|
+
expect(reads).toEqual([join(dir, "delivery")]);
|
|
110
|
+
expect(git(dir, "ls-tree", "-r", "--name-only", "chant/lifecycle")).toBe(`_members/delivery/${CHANGE_SETS_DIR}/${changeSetFile(d.digest)}`);
|
|
111
|
+
|
|
112
|
+
// Stored already: the graph is not read again.
|
|
113
|
+
expect(await storeGateChangeSet({ doc: d, cwd: join(dir, "delivery"), readGraph: async () => { throw new Error("read again"); } })).toEqual({ digest: d.digest, written: false });
|
|
114
|
+
|
|
115
|
+
// chant change-set show, from the workspace root with --member, and from the member's directory.
|
|
116
|
+
const shown = await changeSetShowDocument({ cwd: dir, digest: d.digest, member: ["delivery"] });
|
|
117
|
+
expect(validator(showSchema)(shown)).toBe(true);
|
|
118
|
+
if ("error" in shown) throw new Error(shown.error.message);
|
|
119
|
+
expect(shown.member).toBe("delivery");
|
|
120
|
+
expect(shown.reason).toBeNull();
|
|
121
|
+
expect(validator(changeSetSchema)(shown.changeSet)).toBe(true);
|
|
122
|
+
expect(shown.changeSet!.entries.map((e) => [e.address, e.node])).toEqual([
|
|
123
|
+
["alerts", null],
|
|
124
|
+
["dlq", "delivery/dlq"],
|
|
125
|
+
["events", "delivery/events"],
|
|
126
|
+
["jobs", "delivery/jobs"],
|
|
127
|
+
]);
|
|
128
|
+
const fromMember = await changeSetShowDocument({ cwd: join(dir, "delivery"), digest: d.digest });
|
|
129
|
+
expect(fromMember).toEqual(shown);
|
|
130
|
+
// The flat ledger holds nothing under that digest.
|
|
131
|
+
const flat = await changeSetShowDocument({ cwd: dir, digest: d.digest });
|
|
132
|
+
expect(flat).toMatchObject({ member: null, changeSet: null, reason: null });
|
|
133
|
+
expect(validator(showSchema)(flat)).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("a graph that can't be read stores the document with null nodes and says why", async () => {
|
|
138
|
+
await withTestDir(async (dir) => {
|
|
139
|
+
initRepo(dir);
|
|
140
|
+
clearMemberLedgerCache();
|
|
141
|
+
const d = doc();
|
|
142
|
+
const stored = await storeGateChangeSet({ doc: d, cwd: dir, readGraph: async () => ({ error: "chant graph --format ir exited 1" }) });
|
|
143
|
+
expect(stored.written).toBe(true);
|
|
144
|
+
expect(stored.graph).toBeNull();
|
|
145
|
+
expect(stored.warning).toContain("exited 1");
|
|
146
|
+
const read = await readStoredChangeSet(d.digest, { cwd: dir });
|
|
147
|
+
if (read.status !== "found") throw new Error(read.status);
|
|
148
|
+
expect(read.document.graph).toBeNull();
|
|
149
|
+
expect(read.document.entries.every((e) => e.node === null)).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("show reports a malformed file and a checkout with no git", async () => {
|
|
154
|
+
await withTestDir(async (dir) => {
|
|
155
|
+
const outside = await changeSetShowDocument({ cwd: dir, digest: doc().digest });
|
|
156
|
+
expect(outside).toMatchObject({ error: { code: "not-a-git-repository" } });
|
|
157
|
+
expect(validator(showSchema)(outside)).toBe(true);
|
|
158
|
+
initRepo(dir);
|
|
159
|
+
clearMemberLedgerCache();
|
|
160
|
+
const d = doc();
|
|
161
|
+
await writeLedgerFiles([{ path: `${CHANGE_SETS_DIR}/${changeSetFile(d.digest)}`, content: "{", expectPriorSha: null }], "broken", { cwd: dir });
|
|
162
|
+
const shown = await changeSetShowDocument({ cwd: dir, digest: d.digest });
|
|
163
|
+
expect(shown).toMatchObject({ changeSet: null, reason: { code: "ledger-malformed" } });
|
|
164
|
+
expect(validator(showSchema)(shown)).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("a gate in workspace status links to its stored change set", () => {
|
|
170
|
+
test("a gate whose planDigest names a stored change set carries changeSet; a component gate does not", async () => {
|
|
171
|
+
await withTestDir(async (dir) => {
|
|
172
|
+
initRepo(dir);
|
|
173
|
+
clearMemberLedgerCache();
|
|
174
|
+
const d = doc();
|
|
175
|
+
await storeGateChangeSet({ doc: d, cwd: dir, readGraph: async () => ({ ids: ["jobs"] }) });
|
|
176
|
+
const now = "2026-10-16T00:00:00.000Z";
|
|
177
|
+
const later = "2026-10-17T00:00:00.000Z";
|
|
178
|
+
await appendPendingGate({ op: "pr-7", gate: "pr-apply", planDigest: d.digest, timestamp: now, expiresAt: later }, { cwd: dir });
|
|
179
|
+
const lifecyclePlan = "jcs1-sha256:" + "0".repeat(64);
|
|
180
|
+
await appendPendingGate({ op: "api", gate: "prod-approval", planDigest: lifecyclePlan, environment: "prod", timestamp: now, expiresAt: later }, { cwd: dir });
|
|
181
|
+
const commit = git(dir, "rev-parse", "chant/lifecycle");
|
|
182
|
+
const { gates } = await readMemberGates("_gates", "flat", commit, ["prod"], dir, now);
|
|
183
|
+
const byOp = Object.fromEntries(gates.map((g) => [g.component, g]));
|
|
184
|
+
expect(byOp["pr-7"]).toMatchObject({ state: "pending", planDigest: d.digest, changeSet: d.digest });
|
|
185
|
+
expect(byOp.api.planDigest).toBe(lifecyclePlan);
|
|
186
|
+
expect(byOp.api).not.toHaveProperty("changeSet");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("a release gate's stored change set (#4045)", () => {
|
|
192
|
+
test("names the release and environment it was planned for, and a release gate in status names its release", async () => {
|
|
193
|
+
await withTestDir(async (dir) => {
|
|
194
|
+
initRepo(dir);
|
|
195
|
+
clearMemberLedgerCache();
|
|
196
|
+
const releaseDigest = "jcs1-sha256:" + "a".repeat(64);
|
|
197
|
+
const d = composeChangeSet([lifecyclePlanPart({ member: "queues", plan })], { planned: { releaseDigest, environment: "prod" } });
|
|
198
|
+
await storeGateChangeSet({ doc: d, cwd: dir, readGraph: async () => ({ ids: ["jobs"] }) });
|
|
199
|
+
const read = await readStoredChangeSet(d.digest, { cwd: dir });
|
|
200
|
+
if (read.status !== "found") throw new Error(read.status);
|
|
201
|
+
expect(read.document).toMatchObject({ releaseDigest, environment: "prod" });
|
|
202
|
+
expect(validator(changeSetSchema)(read.document)).toBe(true);
|
|
203
|
+
const shown = await changeSetShowDocument({ cwd: dir, digest: d.digest });
|
|
204
|
+
expect(shown).toMatchObject({ changeSet: { releaseDigest, environment: "prod" } });
|
|
205
|
+
expect(validator(showSchema)(shown)).toBe(true);
|
|
206
|
+
|
|
207
|
+
const now = "2026-10-16T00:00:00.000Z";
|
|
208
|
+
await appendPendingGate(
|
|
209
|
+
{ op: "release", gate: "prod-release", planDigest: d.digest, environment: "prod", releaseDigest, timestamp: now, expiresAt: "2026-10-17T00:00:00.000Z" },
|
|
210
|
+
{ cwd: dir },
|
|
211
|
+
);
|
|
212
|
+
const commit = git(dir, "rev-parse", "chant/lifecycle");
|
|
213
|
+
const { gates } = await readMemberGates("_gates", "flat", commit, ["prod"], dir, now);
|
|
214
|
+
expect(gates).toEqual([expect.objectContaining({ component: "release", releaseDigest, changeSet: d.digest, state: "pending" })]);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|