Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions Governance/policies/RELEASE_BRANCHING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Release Branching Policy

## Purpose

This policy defines the branching model for releases: when a release branch is cut, how
a release candidate is stabilised, and how a fix reaches an older supported line. It
keeps `main` open for ongoing work while a candidate is being hardened, and gives
security fixes a reliable path to the lines users are still running.

## Scope

This policy covers `main` and the `release/*` branches, and the flow of changes between
them. It works with the [Release Cadence](../processes/RELEASE_CADENCE.md) (the
two-week train and the 48-hour freeze), the [Versioning Policy](VERSIONING.md), the
[Tagging Policy](TAGGING.md), and the supported-lines rule in the
[Security Policy](../SECURITY_POLICY.md). It does not cover how contributors organise
their own feature branches.

## Branching Model

The project is **trunk-based**. `main` is the integration branch and the source of every
release. Release branches are short-lived and exist only to stabilise a candidate or to
maintain a supported older line.

| Branch | Purpose | Lifetime |
| --- | --- | --- |
| `main` | All development. Releases are cut from it | Permanent |
| `release/MAJOR.MINOR` | Stabilisation of a candidate, then maintenance of that line while it is supported. Example: `release/1.17` | Until the line is no longer supported |
| Feature and fix branches | One change, opened as a pull request to `main` | Until merged |

There are no long-lived `develop` or per-feature release branches. A release branch is
named after the minor line (`release/x.y`), as the
[Release Manager role](../roles/RELEASE_MANAGER.md) already describes.

All branches that receive merges follow the same rules as `main`: changes arrive through
a pull request with the required checks and approvals, and nobody pushes or force-pushes
directly. Releases are merged by squash or rebase as set out in the
[Merge Strategy Policy](MERGE_STRATEGY.md), which keeps history linear.

## When a Release Branch Is Cut

A release branch is cut when a candidate is frozen for stabilisation, which is the start
of the final 48 hours before promotion described in the Release Cadence. The
[Release Manager](../roles/RELEASE_MANAGER.md) cuts it, with the Maintainers notified.

### Cut steps

1. Confirm that the release issue lists the candidate scope and the commit on `main` to
cut from, and that required checks are green on that commit.
2. Create the branch from that exact commit:

```bash
git switch main && git pull --ff-only
git switch -c release/1.17 <candidate-commit>
git push origin release/1.17
```

3. Record the branch and commit in the release issue.
4. Announce the freeze. From now on `main` accepts the next train's work, and the
candidate changes only on the release branch.

A patch release does not need a new branch. It is made on the existing `release/x.y`
branch for that line.

## Stabilisation

While the candidate is on the branch:

- Only release-blocking fixes are accepted: security, privacy, build, and data-integrity
problems, in line with the freeze rules in the Release Cadence. Features and
refactors wait for the next train.
- Each fix **lands on `main` first**, and is then brought onto the release branch (see
[Backport Flow](#backport-flow)). A fix that exists only on a release branch is a
defect, because the next release would lose it.
- Release-candidate builds are tagged `vX.Y.Z-rc.N` from the branch tip, as set out in
the [Tagging Policy](TAGGING.md), and validated through the required internal, beta,
and store channels.
- Every change on the branch needs the release owner's approval and a fresh validation
pass, as the freeze rules require.

### Promotion

When the Release Manager and the Maintainers approve the candidate, the Release Manager
tags the branch tip `vX.Y.Z` and publishes the release. The `package.json` version on the
branch at that commit matches the tag. The branch then stays as the maintenance line for
`X.Y`.

## Backport Flow

A fix reaches a supported older line by being **cherry-picked from `main`**, never by
merging the release branch into `main`.

1. **Fix on `main` first.** The fix merges to `main` through the normal process.
2. **Decide to backport.** The fix is backported only if it is a security fix, a
data-loss or privacy fix, or a build-breaking fix for a supported line. The line must
be supported: the current line, and the previous line only while the current line is
less than two weeks old, as the Security Policy states. Features are never backported.
3. **Label.** Add `backport/X.Y` to the merged pull request.
4. **Cherry-pick** onto a branch from `release/X.Y`, keeping a reference to the original:

```bash
git switch -c backport/1.16-fix-token-refresh release/1.16
git cherry-pick -x <commit-on-main>
```

The `-x` flag records the source commit in the message. Keep any `Signed-off-by`
lines; add `-s` if cherry-picking would otherwise drop them.
5. **Pull request** to `release/X.Y`, with the same checks and approvals as for `main`,
linking the original pull request.
6. **Patch release.** The Release Manager tags the next patch version on the branch
(for example `v1.16.5`) after the Security Officer signs off a security fix.

A backport that does not apply cleanly is reworked on the release branch and the
difference from the original is explained in the pull request.

## Ending a Line

When a line stops being supported under the Security Policy, the Release Manager records
that in the release issue and removes the `release/X.Y` branch protection and branch.
Its tags and releases are kept permanently.

## Exceptions

An emergency fix follows the [emergency override](APPROVAL_REQUIREMENTS.md#emergency-overrides)
in the approval requirements. It may skip nothing about branch flow: it still lands on
`main` first and is then backported, with the review window shortened rather than the
order changed.

## Current state

This section records what is true today so the policy is not read as describing
machinery that exists.

- The repository has no `release/*` branches. All releases so far have been made from
`main`.
- `.github/workflows/release.yml` runs on pushes to `main` and bumps the version, creates
the release, and builds from there. It does not run for `release/*` branches.
- Branch protection for `release/*` is not configured by anything in the repository.

Adopting this policy therefore means the Release Manager cuts and protects release
branches by hand when the first candidate needs one, and the release workflow is
extended to handle `release/*` as a follow-up owned by the Release Manager. Until then,
a release that does not need stabilisation may continue to be cut from `main` directly.

## Ownership

The release-management team owns this policy. Changes are proposed in a pull request
that touches only the `Governance/` folder.

## Related Documentation

- [Release Cadence](../processes/RELEASE_CADENCE.md)
- [Versioning Policy](VERSIONING.md) and [Tagging Policy](TAGGING.md)
- [Merge Strategy Policy](MERGE_STRATEGY.md)
- [Security Policy](../SECURITY_POLICY.md)
- [Release Manager role](../roles/RELEASE_MANAGER.md)
134 changes: 134 additions & 0 deletions Governance/policies/TAGGING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Git Tagging Policy

## Purpose

This policy defines how releases are marked in Git: how tags are named, who may create
them, and the signing requirement. A tag is the permanent pointer from a published
version to the exact commit it was built from. Consistent, protected tags let anyone
reproduce a release, trace a store build back to source, and trust that a version
number never quietly changes meaning.

## Scope

This policy covers Git tags in this repository, and the GitHub releases attached to
them. It applies to release tags and pre-release tags. Version numbering rules
themselves belong to the [Versioning Policy](VERSIONING.md), and the release schedule to
the [Release Cadence](../processes/RELEASE_CADENCE.md); this policy only governs the
tag.

## Naming Convention

A tag name is the version name with a leading `v`.

| Kind | Format | Example | Defined by |
| --- | --- | --- | --- |
| Stable release | `vMAJOR.MINOR.PATCH` | `v1.16.4` | [Versioning Policy](VERSIONING.md) |
| Alpha pre-release | `vMAJOR.MINOR.PATCH-alpha.N` | `v1.17.0-alpha.1` | Versioning Policy, pre-release conventions |
| Beta pre-release | `vMAJOR.MINOR.PATCH-beta.N` | `v1.17.0-beta.2` | same |
| Release candidate | `vMAJOR.MINOR.PATCH-rc.N` | `v1.17.0-rc.1` | same |

Rules:

- The existing tags (`v1.6.0` through `v1.16.4`) follow the stable format, and new tags
continue it.
- The version in the tag equals the `version` in `package.json` at the tagged commit. A
tag that disagrees with `package.json` is a defect.
- `N` starts at 1 and increases within a stage. A tag name is never reused.
- No other tag names are created for releases. Tags that mark something other than a
version, such as a build or a store submission, are not made; that information
belongs in the release issue.

## Who May Tag

| Tag | Who may create it |
| --- | --- |
| Stable and release-candidate tags | The [Release Manager](../roles/RELEASE_MANAGER.md), or the release automation acting on the Release Manager's behalf |
| Alpha and beta tags | The Release Manager, or a [Maintainer](../roles/MAINTAINER.md) |
| A tag on a security or hotfix release | The Release Manager with the [Security Officer](../roles/SECURITY_OFFICER.md) for security fixes, or a Maintainer for critical bugs, as set out in the Release Manager's sign-off authority |

Nobody else tags. A contributor who needs a release marked asks in the release issue.
Tags are created only on a commit that is already on `main` or on a supported release
branch (see [Release Branching](RELEASE_BRANCHING.md)), after the required checks and
approvals for that commit have passed.

## Tag Contents

A release tag is an **annotated** tag. Its message states:

- the version and the date;
- the release issue it belongs to; and
- the commit it was built from, when the tag is created from a release branch.

Annotated tags carry an author, a date, and a message, and can be signed. Lightweight
tags carry none of these and are not used for releases.

## Signing Requirement

Release tags are **signed** by the person who creates them, with a key registered to
their GitHub account, so that anyone can verify who published a version:

```bash
git tag -s v1.17.0 -m "Release v1.17.0 (release issue #NNNN)"
git tag -v v1.17.0
git push origin v1.17.0
```

This is the release-tag requirement of the [Commit Signing Policy](COMMIT_SIGNING.md), and
the key rules there apply. A signed tag does not replace the commit-level checks already
required for merging, and it is different from the signing keys used for app builds, which are governed by
[App Signing Keys](../domains/APP_SIGNING_KEYS.md).

### Enforcement status

This section states what is and is not true today, so the policy is not mistaken for a
description of a control that exists.

- The existing tags are **lightweight**, not annotated: for example `v1.6.0` through
`v1.9.0` point directly at a commit with no tag object. They carry no message and no
signature.
- `.github/workflows/release.yml` creates the GitHub release and its tag with
`softprops/action-gh-release`, using `tag_name: v<version>`. That step does not
create a signed, annotated tag.

So the signing requirement applies from the date this policy is adopted and is **not yet
met by the release automation**. Until the workflow is changed, the Release Manager
creates the signed annotated tag by hand before publishing the release, and records that
in the release issue. Changing the automation is a follow-up owned by the Release
Manager.

## Immutability

A pushed tag is permanent.

- A tag is never moved, re-pointed, force-pushed, or deleted to "fix" a release.
- If a release is wrong, a new patch version is released with a new tag. The wrong
release is marked as superseded in its release notes.
- A tag may be deleted only if it exposes something that must not be public, such as a
secret committed by mistake, or is a legal requirement. That needs two Maintainers and
the Security Officer, and the reason and the actions taken are recorded in a restricted
issue. The version number is not reused.

Maintainers should enable GitHub tag protection for the pattern `v*` so that creating,
updating, and deleting these tags is restricted to the roles above. As with other
repository settings, this is confirmed at each release.

## Releases and Tags Together

Every published GitHub release has exactly one tag, and every stable tag has a GitHub
release. The release notes follow the [Changelog Policy](CHANGELOG_POLICY.md). A
pre-release tag is marked as a pre-release on GitHub and is never advertised as a stable
version.

## Ownership

The release-management team owns this policy. Changes are proposed in a pull request
that touches only the `Governance/` folder.

## Related Documentation

- [Versioning Policy](VERSIONING.md) and [Changelog Policy](CHANGELOG_POLICY.md)
- [Release Branching Policy](RELEASE_BRANCHING.md)
- [Commit Signing Policy](COMMIT_SIGNING.md)
- [Release Cadence](../processes/RELEASE_CADENCE.md)
- [Release Manager role](../roles/RELEASE_MANAGER.md)
- [App Signing Keys](../domains/APP_SIGNING_KEYS.md)
Loading