diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 8ce1c08ef9..c5f9058c4d 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -12,13 +12,11 @@ name: Release desktop # release and mirrored to R2 (the updater reads R2 first, then the crash worker # release gateway; stable desktop releases own GitHub's repository-wide "latest"). # -# The same build/sign/manifest pipeline serves two channels. An orchestrated -# `desktop-v*` tag (or manual stable recovery) publishes a GitHub release and -# moves R2's latest/ pointer. A manual `channel: preview` dispatch builds with -# -X main.channel=preview -# and uploads only to R2's preview/ pointer — no GitHub release, so Preview never -# shows on the releases page and never touches latest/. The legacy canary/ -# pointer is mirrored for clients released before the channel rename. +# The same build/sign/manifest pipeline serves two channels. Approved +# orchestrators publish Stable or Preview, while manual dispatch supports +# Stable/RC recovery and non-publishing Preview signing checks. Preview advances +# only R2's preview/ pointer and never appears on the GitHub releases page. The +# legacy canary/ pointer is mirrored for clients released before the rename. on: workflow_dispatch: inputs: @@ -32,7 +30,7 @@ on: required: false type: string base_version: - description: "preview: intended next stable version, e.g. 1.5.0" + description: "Preview signing checks: intended next stable version, e.g. 1.5.0" required: false type: string production_signing_smoke: @@ -70,7 +68,7 @@ on: required: true type: string orchestrated: - description: "True only when called by release-stable.yml after approval" + description: "True only when called by an approved release orchestrator" required: false default: false type: boolean @@ -127,9 +125,12 @@ jobs: id: release env: EVENT_NAME: ${{ github.event_name }} + IN_ORCHESTRATED: ${{ inputs.orchestrated }} IN_CHANNEL: ${{ inputs.channel }} IN_TAG: ${{ inputs.tag }} IN_BASE_VERSION: ${{ inputs.base_version }} + IN_PRODUCTION_SIGNING_SMOKE: ${{ inputs.production_signing_smoke }} + IN_SIGNING_PREFLIGHT: ${{ inputs.signing_preflight }} REF_NAME: ${{ github.ref_name }} RUN_NUMBER: ${{ github.run_number }} IN_PREVIEW_NUMBER: ${{ inputs.preview_number }} @@ -186,9 +187,8 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - # Keep Preview fast, but do not make its public, formally signed binaries an - # unreviewed path. The `canary` environment must use the same required - # reviewers as `release`. + # Standalone Preview is limited to non-publishing signing checks, but still + # exercises the production policy behind the protected `canary` environment. environment: ${{ needs.resolve.outputs.channel == 'preview' && 'canary' || 'release' }} steps: - env: @@ -533,10 +533,9 @@ jobs: runs-on: ubuntu-latest permissions: contents: write - # The stable caller has already passed the single GitHub release approval. - # Direct prereleases, manual stable recovery, and Preview all pass the - # appropriate release gate. The SignPath CI identity records each payload - # and installer approval after that GitHub gate. + # Approved orchestrators have already passed the matching GitHub environment. + # Direct prereleases and manual Stable recovery pass release-gate above. The + # SignPath CI identity records each payload and installer approval after it. steps: - uses: actions/checkout@v7 with: diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index 428cc14a7b..1d2dfc4b0a 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -7,12 +7,12 @@ name: Release npm # git tag npm-vX.Y.Z-rc.N # prerelease -> publishes under `next` # git push origin # -# A manual workflow_dispatch can publish either an opt-in canary from the current -# ref or recover an approved stable version from an existing tag. Canary publishes -# to the `canary` dist-tag (npm i reasonix@canary) and never moves -# `next`/`latest`. build.mjs decides the dist-tag: a `-canary.` build is `canary`, -# any other prerelease is `next`, and a stable version is `latest` (#5822 — the -# old "promote latest by hand" rule left it pinned at 0.53.2 and downgraded +# Manual workflow_dispatch recovers only an approved Stable version from an +# existing tag. Public canary publication is owned by release-preview.yml so its +# version remains aligned with CLI, Desktop, and the immutable release marker. +# build.mjs decides the dist-tag: a `-canary.` build is `canary`, any other +# prerelease is `next`, and a stable version is `latest` (#5822 — the old +# "promote latest by hand" rule left it pinned at 0.53.2 and downgraded # `npm update -g` users). The verify step below asserts the tag actually landed. on: push: @@ -20,15 +20,14 @@ on: workflow_dispatch: inputs: channel: - description: "Publish channel" + description: "Standalone npm recovery channel" required: true - default: canary + default: stable type: choice options: - - canary - stable base_version: - description: "Version to publish. Canary appends -canary.; stable publishes exactly this version." + description: "Stable version to recover exactly" required: true type: string tag: @@ -59,7 +58,7 @@ on: required: true type: string orchestrated: - description: "True only when called by release-stable.yml after approval" + description: "True only when called by an approved release orchestrator" required: false default: false type: boolean @@ -77,6 +76,12 @@ on: permissions: contents: read +concurrency: + # Serialize every publisher for one npm dist-tag. In particular, canary + # remains ordered even if another approved orchestrator is added later. + group: release-npm-${{ inputs.channel || 'next' }} + cancel-in-progress: false + jobs: orchestration-guard: name: verify approved orchestrator @@ -109,7 +114,7 @@ jobs: release-gate: name: approve standalone npm release - if: ${{ !inputs.orchestrated && !(github.event_name == 'workflow_dispatch' && inputs.channel == 'canary') }} + if: ${{ !inputs.orchestrated }} runs-on: ubuntu-latest environment: release steps: @@ -120,7 +125,7 @@ jobs: cache-guard: name: cache hit guard needs: [orchestration-guard, release-gate] - if: ${{ always() && !cancelled() && ((inputs.orchestrated && needs.orchestration-guard.result == 'success') || (!inputs.orchestrated && ((github.event_name == 'workflow_dispatch' && inputs.channel == 'canary') || needs.release-gate.result == 'success'))) }} + if: ${{ always() && !cancelled() && ((inputs.orchestrated && needs.orchestration-guard.result == 'success') || (!inputs.orchestrated && needs.release-gate.result == 'success')) }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -137,9 +142,8 @@ jobs: needs: cache-guard if: ${{ always() && !cancelled() && needs.cache-guard.result == 'success' }} runs-on: ubuntu-latest - # The stable caller has already passed the single GitHub release approval. - # Direct prereleases/manual stable recovery pass release-gate above; canaries - # run free. + # Orchestrated releases have already passed their GitHub environment + # approval. Direct prereleases and manual Stable recovery pass release-gate. steps: - uses: actions/checkout@v7 with: @@ -152,8 +156,8 @@ jobs: with: node-version: '22' registry-url: 'https://registry.npmjs.org' - # Tag push uses the npm-v* tag. A canary dispatch synthesizes a - # -canary. version; a manual stable dispatch publishes + # Tag push uses the npm-v* tag. The Preview orchestrator synthesizes a + # -canary. version; a manual Stable dispatch publishes # the requested semver exactly. build.mjs strips the leading `npm-`/`v`. - name: Resolve version id: ver diff --git a/.github/workflows/release-preview.yml b/.github/workflows/release-preview.yml index 961c854a60..f8a09ca5d3 100644 --- a/.github/workflows/release-preview.yml +++ b/.github/workflows/release-preview.yml @@ -13,7 +13,9 @@ on: type: string concurrency: - group: preview-release-${{ inputs.tag }} + # Every Preview version advances the same public CLI, Desktop, and npm + # channels. Keep the whole all-surface event serialized across versions. + group: preview-release cancel-in-progress: false permissions: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index edc88626ac..78cbf09fdc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,24 +1,22 @@ name: Release -# Native CLI binary line. Stable releases are called by release-stable.yml after -# its single GitHub `release`-environment approval. Preview uses immutable -# vX.Y.Z-preview.N tags, publishes a GitHub prerelease behind the same `canary` -# gate as Desktop Preview, and never moves Homebrew or GitHub Latest. Other -# standalone prereleases remain internal stable-channel checkpoints behind the -# `release` gate. +# Native CLI binary line. Stable and Preview releases are called by their +# approved all-surface orchestrators. Manual dispatch remains available only for +# Stable/RC recovery; standalone prereleases remain internal stable-channel +# checkpoints behind the `release` gate. on: workflow_dispatch: inputs: channel: - description: "Native CLI release channel" + description: "Standalone CLI recovery channel" required: true - # Preserve existing tag-only recovery dispatches as Stable. Preview must - # be an explicit operator choice or arrive through the tag relay. + # Public Preview recovery reruns release-preview.yml so every surface and + # its immutable release-event marker stay aligned. default: stable type: choice - options: [stable, preview] + options: [stable] tag: - description: "Existing protected CLI tag (Preview: v1.18.0-preview.1; Stable/RC: v1.18.0-rc.1)" + description: "Existing protected Stable/RC CLI tag (for example v1.18.0 or v1.18.0-rc.1)" required: true type: string workflow_call: @@ -41,7 +39,7 @@ on: required: true type: string orchestrated: - description: "True only when called by release-stable.yml after approval" + description: "True only when called by an approved release orchestrator" required: false default: false type: boolean @@ -149,7 +147,7 @@ jobs: needs: resolve if: ${{ !inputs.orchestrated }} runs-on: ubuntu-latest - environment: ${{ needs.resolve.outputs.channel == 'preview' && 'canary' || 'release' }} + environment: release steps: - env: RELEASE_TAG: ${{ needs.resolve.outputs.tag }} diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 3d79651f62..93a2f66903 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -213,6 +213,12 @@ strict separation from repository writers is required. npm, and Desktop tags to remain aligned on an ancestor of current `main-v2`, then uses the same single approval and postflight. Never move or recreate the published tags to pick up a workflow fix. +- Recover an interrupted Preview release by rerunning **Release preview** from + protected `main-v2` with the existing `vX.Y.Z-preview.N` tag. Do not dispatch + the CLI, Desktop, or npm publisher directly: those paths cannot create the + all-surface `release-event.json` marker and are blocked from public Preview + publication. Standalone Desktop Preview dispatch remains available only for + non-publishing SignPath preflight and production-signing smoke checks. - Windows release signing uses SignPath trusted-build, origin verification, and malware scanning. Keep the checked-in `windows-payload` and `windows-installer-v2` artifact configurations synchronized with the matching diff --git a/scripts/release-workflows.test.sh b/scripts/release-workflows.test.sh index 71d91e8bb7..06b03f4d83 100755 --- a/scripts/release-workflows.test.sh +++ b/scripts/release-workflows.test.sh @@ -35,6 +35,11 @@ grep -Fq "workflow_id: preview ? 'release-preview.yml' : 'release.yml'" \ "$repo_root/.github/workflows/release-cli-trigger.yml" grep -Eq "preview\\\\\." "$repo_root/.github/workflows/release-cli-trigger.yml" grep -Eq '^name: Release preview$' "$repo_root/.github/workflows/release-preview.yml" +grep -Eq '^ group: preview-release$' "$repo_root/.github/workflows/release-preview.yml" +if grep -Fq 'group: preview-release-${{ inputs.tag }}' "$repo_root/.github/workflows/release-preview.yml"; then + echo "Preview releases must serialize across versions" >&2 + exit 1 +fi grep -Eq '^ environment: canary$' "$repo_root/.github/workflows/release-preview.yml" grep -Eq '^ signpath-preflight:$' "$repo_root/.github/workflows/release-preview.yml" grep -Eq 'signing_preflight: true' "$repo_root/.github/workflows/release-preview.yml" @@ -57,11 +62,10 @@ for workflow in release.yml release-npm.yml release-desktop.yml; do grep -Eq 'release-\{1\}\.yml' "$repo_root/.github/workflows/$workflow" grep -Eq "needs\.cache-guard\.result == 'success'" "$repo_root/.github/workflows/$workflow" done -grep -Eq 'options: \[stable, preview\]' "$repo_root/.github/workflows/release.yml" +grep -Eq 'options: \[stable\]' "$repo_root/.github/workflows/release.yml" grep -A6 -E '^ channel:' "$repo_root/.github/workflows/release.yml" | grep -Eq 'default: stable' -grep -Eq "needs\.resolve\.outputs\.channel == 'preview'.*'canary'.*'release'" \ - "$repo_root/.github/workflows/release.yml" +grep -Eq '^ environment: release$' "$repo_root/.github/workflows/release.yml" grep -Eq 'GORELEASER_CURRENT_TAG:.*needs\.resolve\.outputs\.tag' \ "$repo_root/.github/workflows/release.yml" grep -Eq 'bash scripts/resolve-cli-release\.sh' "$repo_root/.github/workflows/release.yml" @@ -98,6 +102,20 @@ if grep -Fq 'ref: ${{ inputs.approved_sha || inputs.tag || github.ref }}' \ fi grep -Fq "group: release-desktop-\${{ (inputs.channel == 'preview' || inputs.channel == 'canary') && 'preview' || 'stable' }}" \ "$repo_root/.github/workflows/release-desktop.yml" +grep -Eq 'IN_PRODUCTION_SIGNING_SMOKE:.*inputs\.production_signing_smoke' \ + "$repo_root/.github/workflows/release-desktop.yml" +grep -Eq 'IN_SIGNING_PREFLIGHT:.*inputs\.signing_preflight' \ + "$repo_root/.github/workflows/release-desktop.yml" +grep -Fq "group: release-npm-\${{ inputs.channel || 'next' }}" \ + "$repo_root/.github/workflows/release-npm.yml" +sed -n '/^ workflow_dispatch:/,/^ workflow_call:/p' "$repo_root/.github/workflows/release-npm.yml" | + grep -Eq 'default: stable' +if sed -n '/^ workflow_dispatch:/,/^ workflow_call:/p' "$repo_root/.github/workflows/release-npm.yml" | + grep -Eq '^ - canary$'; then + echo "Standalone npm dispatch must not expose public Canary publication" >&2 + exit 1 +fi +grep -Fq 'if: ${{ !inputs.orchestrated }}' "$repo_root/.github/workflows/release-npm.yml" grep -Eq 'signing-policy-slug: release-signing' "$repo_root/.github/workflows/release-desktop.yml" if grep -Eq 'signing-policy-slug:.*test-signing' "$repo_root/.github/workflows/release-desktop.yml"; then echo "public desktop workflow must not use the SignPath test certificate" >&2 @@ -1072,14 +1090,15 @@ grep -Eq '^tag=desktop-v1\.2\.3$' "$test_root/desktop-stable.out" grep -Eq '^version=v1\.2\.3$' "$test_root/desktop-stable.out" grep -Eq '^notes_version=v1\.2\.3$' "$test_root/desktop-stable.out" -EVENT_NAME=workflow_dispatch IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=42 \ +EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 \ + IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=999 IN_PREVIEW_NUMBER=42 \ GITHUB_OUTPUT="$test_root/desktop-preview.out" bash "$repo_root/scripts/resolve-desktop-release.sh" grep -Eq '^version=v1\.3\.0-preview\.42$' "$test_root/desktop-preview.out" grep -Eq '^tag=desktop-v1\.3\.0-preview\.42$' "$test_root/desktop-preview.out" grep -Eq '^channel=preview$' "$test_root/desktop-preview.out" grep -Eq '^notes_version=v1\.3\.0-preview\.42$' "$test_root/desktop-preview.out" -if EVENT_NAME=workflow_dispatch IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 \ +if EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 \ IN_TAG=desktop-v1.3.0-preview.42 REF_NAME=main-v2 RUN_NUMBER=42 \ GITHUB_OUTPUT="$test_root/desktop-preview-tagged.out" \ bash "$repo_root/scripts/resolve-desktop-release.sh" \ @@ -1090,14 +1109,31 @@ fi grep -Eq 'Desktop Preview versions are synthesized from protected main-v2' \ "$test_root/desktop-preview-tagged.log" -# Legacy automation may still dispatch `canary`; normalize it to Preview without -# exposing a third user-facing release channel. -EVENT_NAME=workflow_dispatch IN_CHANNEL=canary IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=43 \ +# Legacy signing automation may still dispatch `canary`; normalize it to Preview +# without exposing a third public release path. +EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_SIGNING_PREFLIGHT=true \ + IN_CHANNEL=canary IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=43 \ GITHUB_OUTPUT="$test_root/desktop-canary-compat.out" bash "$repo_root/scripts/resolve-desktop-release.sh" grep -Eq '^version=v1\.3\.0-preview\.43$' "$test_root/desktop-canary-compat.out" grep -Eq '^tag=desktop-v1\.3\.0-preview\.43$' "$test_root/desktop-canary-compat.out" grep -Eq '^channel=preview$' "$test_root/desktop-canary-compat.out" +EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_PRODUCTION_SIGNING_SMOKE=true \ + IN_CHANNEL=preview IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=44 \ + GITHUB_OUTPUT="$test_root/desktop-preview-smoke.out" bash "$repo_root/scripts/resolve-desktop-release.sh" +grep -Eq '^version=v1\.3\.0-preview\.44$' "$test_root/desktop-preview-smoke.out" + +if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=preview \ + IN_BASE_VERSION=1.3.0 IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=45 \ + GITHUB_OUTPUT="$test_root/desktop-preview-direct.out" \ + bash "$repo_root/scripts/resolve-desktop-release.sh" \ + >"$test_root/desktop-preview-direct.log" 2>&1; then + echo "standalone public Desktop Preview unexpectedly passed" >&2 + exit 1 +fi +grep -Eq 'public Desktop Preview releases must be dispatched by release-preview.yml' \ + "$test_root/desktop-preview-direct.log" + EVENT_NAME=push IN_CHANNEL='' IN_TAG='' REF_NAME=desktop-v1.4.0-rc.1 RUN_NUMBER=50 \ GITHUB_OUTPUT="$test_root/desktop-rc.out" bash "$repo_root/scripts/resolve-desktop-release.sh" grep -Eq '^prerelease=true$' "$test_root/desktop-rc.out" @@ -1121,20 +1157,26 @@ if EVENT_NAME=workflow_dispatch IN_CHANNEL=stable IN_TAG='' REF_NAME=main-v2 RUN fi grep -Eq 'stable dispatch requires tag' "$test_root/desktop-missing-tag.log" -EVENT_NAME=push IN_CHANNEL='' IN_TAG='' REF_NAME=v1.3.0-preview.42 \ - GITHUB_OUTPUT="$test_root/cli-preview-push.out" bash "$repo_root/scripts/resolve-cli-release.sh" -grep -Eq '^tag=v1\.3\.0-preview\.42$' "$test_root/cli-preview-push.out" -grep -Eq '^version=1\.3\.0-preview\.42$' "$test_root/cli-preview-push.out" -grep -Eq '^base_version=1\.3\.0$' "$test_root/cli-preview-push.out" -grep -Eq '^notes_version=v1\.3\.0-preview\.42$' "$test_root/cli-preview-push.out" -grep -Eq '^channel=preview$' "$test_root/cli-preview-push.out" -grep -Eq '^prerelease=true$' "$test_root/cli-preview-push.out" +EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=preview IN_TAG=v1.3.0-preview.42 \ + REF_NAME=main-v2 GITHUB_OUTPUT="$test_root/cli-preview-call.out" \ + bash "$repo_root/scripts/resolve-cli-release.sh" +grep -Eq '^tag=v1\.3\.0-preview\.42$' "$test_root/cli-preview-call.out" +grep -Eq '^version=1\.3\.0-preview\.42$' "$test_root/cli-preview-call.out" +grep -Eq '^base_version=1\.3\.0$' "$test_root/cli-preview-call.out" +grep -Eq '^notes_version=v1\.3\.0-preview\.42$' "$test_root/cli-preview-call.out" +grep -Eq '^channel=preview$' "$test_root/cli-preview-call.out" +grep -Eq '^prerelease=true$' "$test_root/cli-preview-call.out" -EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=preview \ +if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=preview \ IN_TAG=v1.3.0-preview.42 REF_NAME=main-v2 CALLER_REF=refs/heads/main-v2 \ CALLER_REF_PROTECTED=true GITHUB_OUTPUT="$test_root/cli-preview-dispatch.out" \ - bash "$repo_root/scripts/resolve-cli-release.sh" -grep -Eq '^channel=preview$' "$test_root/cli-preview-dispatch.out" + bash "$repo_root/scripts/resolve-cli-release.sh" \ + >"$test_root/cli-preview-dispatch.log" 2>&1; then + echo "standalone public CLI Preview unexpectedly passed" >&2 + exit 1 +fi +grep -Eq 'public CLI Preview releases must be dispatched by release-preview.yml' \ + "$test_root/cli-preview-dispatch.log" EVENT_NAME=push IN_CHANNEL='' IN_TAG='' REF_NAME=v1.3.0-rc.1 \ GITHUB_OUTPUT="$test_root/cli-rc.out" bash "$repo_root/scripts/resolve-cli-release.sh" @@ -1178,6 +1220,21 @@ EVENT_NAME=push IN_ORCHESTRATED=true IN_CHANNEL=stable IN_BASE_VERSION=1.5.0 \ bash "$repo_root/scripts/resolve-npm-release.sh" grep -Eq '^arg=v1\.5\.0$' "$test_root/npm-stable.out" +EVENT_NAME=workflow_call IN_ORCHESTRATED=true IN_CHANNEL=canary IN_BASE_VERSION=1.5.0 \ + IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=999 IN_PREVIEW_NUMBER=42 \ + GITHUB_OUTPUT="$test_root/npm-canary.out" bash "$repo_root/scripts/resolve-npm-release.sh" +grep -Eq '^arg=v1\.5\.0-canary\.42$' "$test_root/npm-canary.out" + +if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=canary IN_BASE_VERSION=1.5.0 \ + IN_TAG='' REF_NAME=main-v2 RUN_NUMBER=52 GITHUB_OUTPUT="$test_root/npm-canary-direct.out" \ + bash "$repo_root/scripts/resolve-npm-release.sh" \ + >"$test_root/npm-canary-direct.log" 2>&1; then + echo "standalone public npm Canary unexpectedly passed" >&2 + exit 1 +fi +grep -Eq 'public npm Canary releases must be dispatched by release-preview.yml' \ + "$test_root/npm-canary-direct.log" + if EVENT_NAME=workflow_dispatch IN_ORCHESTRATED=false IN_CHANNEL=stable IN_BASE_VERSION=1.5.0 \ IN_TAG=npm-v1.5.1 REF_NAME=main-v2 RUN_NUMBER=52 GITHUB_OUTPUT="$test_root/npm-mismatch.out" \ bash "$repo_root/scripts/resolve-npm-release.sh" >"$test_root/npm-mismatch.log" 2>&1; then diff --git a/scripts/resolve-cli-release.sh b/scripts/resolve-cli-release.sh index 50b3dc3b3e..77bc644688 100644 --- a/scripts/resolve-cli-release.sh +++ b/scripts/resolve-cli-release.sh @@ -66,6 +66,11 @@ if [ -n "$requested_channel" ]; then fi fi +if [ "$channel" = "preview" ] && [ "${IN_ORCHESTRATED:-false}" != "true" ]; then + echo "::error::public CLI Preview releases must be dispatched by release-preview.yml" >&2 + exit 1 +fi + { echo "tag=$tag" echo "version=$version" diff --git a/scripts/resolve-desktop-release.sh b/scripts/resolve-desktop-release.sh index 74e233fe73..54e272b5cc 100644 --- a/scripts/resolve-desktop-release.sh +++ b/scripts/resolve-desktop-release.sh @@ -5,10 +5,10 @@ # # stable: from a desktop-v* prerelease tag push, a manual dispatch with `tag`, # or the stable release orchestrator's workflow_call input. -# preview: a manual dispatch with channel=preview (legacy canary accepted); -# version is synthesized from base_version + the monotonic run_number, -# tag is the matching immutable asset directory, and it is always a -# prerelease. +# preview: public publication requires the approved Preview orchestrator; +# standalone Preview is limited to non-publishing signing checks. +# Legacy canary input is accepted for those checks. The version uses +# the orchestrator ordinal or, for a signing check, the run number. set -euo pipefail stable_semver_re='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' @@ -16,6 +16,12 @@ release_semver_re='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-([0-9A-Za preview_semver_re='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)-preview\.(0|[1-9][0-9]*)$' if [ "${IN_CHANNEL:-stable}" = "preview" ] || [ "${IN_CHANNEL:-stable}" = "canary" ]; then + if [ "${IN_ORCHESTRATED:-false}" != "true" ] && \ + [ "${IN_PRODUCTION_SIGNING_SMOKE:-false}" != "true" ] && \ + [ "${IN_SIGNING_PREFLIGHT:-false}" != "true" ]; then + echo "::error::public Desktop Preview releases must be dispatched by release-preview.yml" >&2 + exit 1 + fi if [ -n "${IN_TAG:-}" ]; then echo "::error::Desktop Preview versions are synthesized from protected main-v2; tag must be empty" >&2 exit 1 diff --git a/scripts/resolve-npm-release.sh b/scripts/resolve-npm-release.sh index c2d27d7004..773369bffa 100755 --- a/scripts/resolve-npm-release.sh +++ b/scripts/resolve-npm-release.sh @@ -15,6 +15,10 @@ if [ "${EVENT_NAME:-}" = "workflow_dispatch" ] || [ "${IN_ORCHESTRATED:-false}" exit 1 fi if [ "${IN_CHANNEL:-stable}" = "canary" ]; then + if [ "${IN_ORCHESTRATED:-false}" != "true" ]; then + echo "::error::public npm Canary releases must be dispatched by release-preview.yml" >&2 + exit 1 + fi preview_number="${IN_PREVIEW_NUMBER:-${RUN_NUMBER:-}}" if [[ ! "$preview_number" =~ ^(0|[1-9][0-9]*)$ ]]; then echo "::error::canary preview number must be a non-negative integer, got: $preview_number" >&2