feat(release): support version-matched mobile stable branches (#8430)

Mobile candidates currently require the current `main` commit, so a
stable release branch cannot publish through the existing protected tag
workflow.

Add optional `--source-branch mobile-vMAJOR.MINOR` selection while
preserving `main` as the default. Both the command and App-backed
publisher reject a stable branch whose major/minor differs from the
requested version. Candidates retain exact-tip checks and immutable
annotated tags; the private build pipeline is unchanged.

Document the temporary stable-only distribution policy and explicit
per-fix backport decisions. No candidate, build, or upload is triggered
by this change. No duplicate implementation was found among open release
PRs.

Stable publication now checks active review and status-check protection
in both the operator and publisher. The process permits approved
backport PRs and stable-only corrections for regressions caused by
earlier backports. Administrator activation and verification of the
no-bypass ruleset remain outstanding; non-admin API responses hide the
bypass actor list.

### Validation

- Full local `just ci` passed during preparation; the final changes
affect shell release tooling, its workflow inputs, CI path detection,
and documentation.
- Publisher contract passed, including version-matched stable sources
and mismatched branch rejection.
- Operator contract covers default main, a divergent stable commit,
missing or mismatched sources, immutable sequencing, and publication
races. The final operator contract run passed.


- Updated publisher and operator contracts pass, including 17
weakened-policy cases and rejection of unavailable protection. The live,
currently unprotected stable branch is rejected.

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
Tom Brow
2026-10-18 18:04:04 -07:00
committed by GitHub
parent 8f2c7710e9
commit 30ca980b49
9 changed files with 291 additions and 59 deletions

View File

@@ -1,7 +1,8 @@
# Releasing Buzz
Buzz has three independent release lanes. Desktop and relay use release PRs.
Mobile uses immutable release-candidate tags cut directly from remote `main`:
Mobile uses immutable release-candidate tags cut from remote `main` by default,
with an optional stable source branch:
| Lane | Entry point | Artifact |
|------|-------------|----------|
@@ -38,7 +39,8 @@ scripts/mobile-release.sh candidate 0.5.0
Desktop uses an immutable generated candidate PR; relay continues using its
metadata PR. Mobile does not. Each `mobile-vX.Y.Z-rc.N` tag is an immutable
candidate and the artifact of record.
There is no mobile release branch, stable mobile tag alias, finalization step,
Optional stable branches use `mobile-vMAJOR.MINOR`. There is no
stable mobile tag alias, finalization step,
or mobile GitHub Release.
---
@@ -153,10 +155,47 @@ RC tags. For example, iOS can ship `mobile-v0.5.0-rc.2` while Android ships
There is intentionally no single selected or final candidate for the marketing
version.
The simplification trades away a separate stabilization line. Unrelated commits
that reach `main` become part of every later candidate, and there is no retained
hotfix branch or branch-ancestry history. Add a dedicated hotfix flow later if a
release actually needs isolation from `main`.
### Optional stable release branches
For a release that needs isolation from ongoing development, create a branch
such as `mobile-v0.25` from an explicitly selected, verified source commit.
Publish candidates from its current remote tip:
```bash
scripts/mobile-release.sh candidate 0.25.0 --source-branch mobile-v0.25
```
The branch must match the marketing version's major and minor components:
`mobile-v0.25` accepts 0.25.x, but rejects 0.26.x and 1.25.x. Without the option,
the source remains `main`. Both the operator and publisher validate the source;
a missing branch or changed tip fails rather than falling back to `main`.
The GitHub workflow itself continues to run from reviewed `main`. The private
pipeline still builds only the resulting immutable candidate tag.
For the October 2026 stabilization period, all distributed mobile builds,
including Company Portal dogfood builds, must come from `mobile-v0.25` until
after the conference. Do not upload builds from `main` or a later minor version
that would displace the stable dogfood build. The branch starts from
`mobile-v0.24.0-rc.2`, the source of uploaded iOS 0.24.0 (7).
General fix PRs must target `main` and land there first. At each fix landing,
ask the release owner whether to
backport the merged commit to the stable branch. For each explicitly approved
fix, cherry-pick with `-x --signoff` onto a backport branch and open a backport-only
PR targeting the stable branch. Require review and passing checks before merging
that PR. Preserve the original commit provenance and validate the resulting
stable source. A correction for a regression introduced by an earlier backport
may target the stable branch directly; identify the faulty backport and keep the
correction scoped to that regression. The same review and checks apply.
Never merge `main` wholesale.
Remote agents and App Clip are post-conference work. There is no preselected
fix list and no requirement to create a stable branch for every minor release.
The initial 0.25.0 candidate uses an empty commit above that baseline to verify
that the existing pipeline builds a commit outside `main`. Verify identical
source trees, absence of that commit from remote `main`, and the exact tag,
commit, marketing version and build number in the uploaded artifacts. Upload
and external TestFlight review do not establish production App Store approval.
`mobile/pubspec.yaml` keeps `0.0.0+1` only as a valid, visibly non-release
fallback for local development and validation builds. Release jobs always
@@ -317,8 +356,15 @@ Mobile candidate publication requires workflow-dispatch access and the existing
release App because strict tag protection denies direct human creation. The App
must be installed on `block/buzz`, have Contents write and Metadata read, and
retain an `always` bypass on the immutable `mobile-v*` tag rules. It does not
require GitHub Releases permissions, repository Administration permission, or a
mobile release-branch ruleset. The publisher validates the App token's effective
require GitHub Releases permissions or repository Administration permission.
Stable sources additionally require active branch ruleset `19321162` covering
`refs/heads/mobile-v*`, with no exclusions or bypass actors, deletion and
force-push prevention, at least one approval, stale-approval dismissal, last-push
approval, resolved review threads, and strict required `Mobile` and `DCO Check`
checks. An administrator must configure and audit the empty bypass list; GitHub
hides that list from non-admin tokens. Both operator and publisher verify the
public policy and their effective lack of branch bypass on every publication.
The publisher validates the App token's effective
`current_user_can_bypass` value rather than reading the ruleset's hidden bypass
actor list.
@@ -343,18 +389,19 @@ Commit or stash your changes before running the release recipe.
### New commits land after publishing a mobile candidate
Run `scripts/mobile-release.sh candidate <version>` again after the intended
fix reaches remote `main`. It publishes a new immutable RC tag at the new exact
fix reaches the intended remote source branch. Use the same `--source-branch`
option for a stable release. The command publishes a new immutable RC tag at the new exact
remote commit. Continue referring to each tested or shipped platform artifact by
its own exact tag.
### `scripts/mobile-release.sh candidate` fails because `main` moved during publication
### `scripts/mobile-release.sh candidate` fails because the source branch moved during publication
The App-backed workflow may already have published the requested immutable RC
at the prior `main` tip before the operator command detects the race. Do not
move or delete that tag, and do not treat it as the candidate for current
`main`. Inspect the run URL from the command output, then rerun
at the prior source branch tip before the operator command detects the race. Do not
move or delete that tag, and do not treat it as the candidate for the current
source branch. Inspect the run URL from the command output, then rerun
`scripts/mobile-release.sh candidate <version>` to publish the next RC from the
new current `main` tip.
new current source branch tip, preserving the source-branch option.
### A mobile candidate command selects the wrong RC number