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/<version>` 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=<existing-candidate-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 <arosenzweig@squareup.com>
Co-authored-by: BigBoss <19a7c9f8477c0dd12d428c2b2faa352556cd13b3ec3d717498b60f85a57a90a9@buzz.block.builderlab.xyz>
This commit is contained in:
Alex Rosenzweig
2026-11-09 11:36:32 -06:00
committed by GitHub
parent c502e53e63
commit 34bab2f64b
2 changed files with 44 additions and 3 deletions

View File

@@ -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/<version>)
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"

View File

@@ -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 <version>` 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.