From 34bab2f64b6a6417323b4dc948b2e3e898d26617 Mon Sep 17 00:00:00 2001 From: Alex Rosenzweig <64241648+shellz-n-stuff@users.noreply.github.com> Date: Mon, 9 Nov 2026 11:36:32 -0600 Subject: [PATCH] feat(ci): allow admin dispatch of desktop candidate validation (#9288) ## Summary Desktop Release Candidate only supports PR events today. Add a manual dispatch with a required version input that validates the existing `version-bump/` branch. Require the workflow from `main` and current repository admin permission for both the original actor and any rerunner before checkout; API failures deny the run. Existing PR validation and read-only token permissions are preserved. GitHub can still accept a dispatch from a writer; the workflow rejects that run before candidate checkout. This validates an existing candidate and does not create or publish a release. Document the command in `RELEASING.md`. ### Related issue None found in open issue/PR title searches for "desktop release candidate". ### Testing - `actionlint .github/workflows/desktop-release-candidate.yml` passed. - `bash scripts/test-desktop-release-candidate.sh` passed, including the new authorization tests executed from the production workflow shell with only the GitHub API mocked. Covers admin access, all lesser roles, missing permission responses, API failures, rerunners, and non-main dispatches. - Mutation checks confirmed that removing the admin check, rerunner check, or main-ref restriction fails the new tests. - Self-review completed with no blocking findings. - `just ci` is running locally; results will be updated here. - Hosted manual dispatch has not been exercised: the new guard requires dispatch from `main`, where this change is not merged. Human verification is pending, so this PR remains a draft under `AGENTS.md`. Once available on `main`, an admin can test with `gh workflow run desktop-release-candidate.yml --repo block/buzz --ref main -f version=`. Expect permission checks to pass and the immutable candidate validator to run against that candidate. A writer or a dispatch selecting another workflow branch should fail before checkout. Manual runs do not replace the candidate PR's required checks. --------- Signed-off-by: Alex Rosenzweig Co-authored-by: BigBoss <19a7c9f8477c0dd12d428c2b2faa352556cd13b3ec3d717498b60f85a57a90a9@buzz.block.builderlab.xyz> --- .../workflows/desktop-release-candidate.yml | 33 +++++++++++++++++-- RELEASING.md | 14 ++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/.github/workflows/desktop-release-candidate.yml b/.github/workflows/desktop-release-candidate.yml index 61ccc800af..9533e0c2a6 100644 --- a/.github/workflows/desktop-release-candidate.yml +++ b/.github/workflows/desktop-release-candidate.yml @@ -3,6 +3,12 @@ name: Desktop Release Candidate on: pull_request: branches: [main] + workflow_dispatch: + inputs: + version: + description: Existing candidate version to validate (version-bump/) + required: true + type: string permissions: contents: read @@ -12,17 +18,38 @@ jobs: validate: name: Desktop Release Candidate runs-on: ubuntu-latest + timeout-minutes: 10 steps: + - name: Authorize manual candidate validation + if: github.event_name == 'workflow_dispatch' + env: + GH_TOKEN: ${{ github.token }} + DISPATCH_REF: ${{ github.ref }} + TRIGGERING_ACTOR: ${{ github.triggering_actor }} + run: | + set -euo pipefail + if [[ "$DISPATCH_REF" != refs/heads/main ]]; then + echo "::error::Dispatch candidate validation from main." + exit 1 + fi + # Re-runs retain the original actor; authorize the re-runner too. + for actor in "$GITHUB_ACTOR" "$TRIGGERING_ACTOR"; do + permission=$(gh api "repos/$GITHUB_REPOSITORY/collaborators/$actor/permission" --jq '.permission') + if [[ "$permission" != admin ]]; then + echo "::error::Manual candidate validation requires repository admin permission." + exit 1 + fi + done - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/heads/version-bump/{0}', inputs.version) || github.event.pull_request.head.sha }} fetch-depth: 0 persist-credentials: false - name: Validate immutable desktop candidate - if: startsWith(github.event.pull_request.head.ref, 'version-bump/') + if: github.event_name == 'workflow_dispatch' || startsWith(github.event.pull_request.head.ref, 'version-bump/') env: GH_TOKEN: ${{ github.token }} - VERSION: ${{ github.event.pull_request.head.ref }} + VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.event.pull_request.head.ref }} run: | VERSION="${VERSION#version-bump/}" scripts/desktop_release.py validate --candidate HEAD --version "$VERSION" --repo "$GITHUB_REPOSITORY" diff --git a/RELEASING.md b/RELEASING.md index 4279b80ec3..e50ead2c08 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -51,6 +51,20 @@ or mobile GitHub Release. ### Desktop +Repository admins can manually validate an existing candidate with: + +```sh +gh workflow run desktop-release-candidate.yml --repo block/buzz --ref main -f version=0.5.3 +``` + +This checks out `version-bump/0.5.3` and runs the same immutable-candidate +validator as the PR check. Dispatch from `main`; both the original caller and +anyone rerunning the workflow must currently have repository admin permission. +GitHub allows users with write access to dispatch workflows, but this workflow +fails their manual runs before checkout. Permission lookup failures also stop +the run. Manual validation does not create a candidate, publish a release, or +replace the required checks on the candidate PR. + 1. Run `just release-desktop ` from a clean, up-to-date `main` checkout. The script creates one deterministic candidate commit and records both its frozen base and the verified prior release ledger in candidate metadata.