diff --git a/.github/actions/README.md b/.github/actions/README.md index 5f1a38b..2fc045e 100644 --- a/.github/actions/README.md +++ b/.github/actions/README.md @@ -350,7 +350,7 @@ pr-review: ## Release actions Three composite actions implement the org release flow — trunk-first -candidate, settle-by-PR, publish-once. They are language-agnostic: the only +candidate, environment-gated settle (or settle-by-PR), publish-once. They are language-agnostic: the only repo-specific inputs are where the version lives (`version_file` + `version_pattern`: `plain`, `toml`, `json`), an optional `bump_command` for whatever else must move with the version (`cargo update --workspace` for a @@ -386,16 +386,21 @@ Requirements in the consumer repo: - Org variable `MEGA_MAXWELL_CLIENT_ID` (the Maxwell app's client id) / secret `MEGA_MAXWELL_PK`. PRs and tag pushes must come from an App token: `GITHUB_TOKEN` does not trigger downstream workflows. -- A reviewed settle PR is the human gate for creating a tag: the - `release-*` branch ruleset below requires it. No environment approval is - needed on the publish job (an `environment:` with required reviewers can - be added by repos that want a second, separate approver). +- The human gate for creating a tag is the settle dispatch: the templates + put `environment: release` on the settle job, so its required reviewers + approve the run before it starts (create the environment with reviewers, + `prevent self-review` off if the dispatcher approves, and a deployment + branch policy of the default branch only), and the action's + `settlers: admin` checks the dispatching actor again. A repo that settles + by PR instead (no `settle_mode`) has the reviewed settle PR as its gate: + the `release-*` branch ruleset below requires one. - A tag ruleset for `v*` (no creation/deletion/force-push) with the app as a bypass actor, so `release-publish` is the only tag creator. -- A branch ruleset for `release-*` requiring PRs, so settlement is always a - reviewed merge. Drift after a settle PR is opened is caught by - `release-publish` itself (it refuses if the branch tip at merge is not the - settled SHA), so no "up to date" status check is needed. +- A branch ruleset for `release-*` requiring PRs, with the app as a bypass + actor: nothing but the app's settle commit reaches a release branch + without a reviewed PR. In PR mode, drift after a settle PR is opened is + caught by `release-publish` itself (it refuses if the branch tip at merge + is not the settled SHA), so no "up to date" status check is needed. - `gh` and `python3` on the runner (any GitHub-hosted image). - The cut and publish stages only accept PRs opened by the app identity (`pr_author`, default `mega-maxwell[bot]`); the templates also gate the diff --git a/workflow-templates/release-publish.yml b/workflow-templates/release-publish.yml index 7d08f1c..4eef8eb 100644 --- a/workflow-templates/release-publish.yml +++ b/workflow-templates/release-publish.yml @@ -8,6 +8,11 @@ name: Release Publish # # This file must exist on the release branch (it does, when the branch is cut # from a default branch that has it). +# +# Only settlement by PR reaches this workflow. With `settle_mode: direct` in +# release-settle.yml (the template's default) the settle job publishes +# itself and nothing here ever fires: the job gate above rejects any settle +# PR the app did not open. on: pull_request: diff --git a/workflow-templates/release-settle.yml b/workflow-templates/release-settle.yml index c70acb4..3707c94 100644 --- a/workflow-templates/release-settle.yml +++ b/workflow-templates/release-settle.yml @@ -1,10 +1,21 @@ name: Release Settle -# Propose settling a release candidate. Dispatch from any branch with the +# Settle a release candidate. Dispatch from the default branch with the # version and the full SHA of the release-branch tip; the shared action # verifies it, generates release notes from the commits since the previous -# tag, writes them to the changelog, and opens a settle PR onto the release -# branch. Merging that PR is the settlement decision (release-publish tags it). +# tag, commits them to the changelog on the release branch, and publishes: +# annotated tag at that commit, GitHub Release with the notes. +# +# The dispatch is the settlement decision, gated twice: the `release` +# environment on the job (its required reviewers approve the run before it +# starts; give it a deployment branch policy of the default branch only) and +# `settlers` in the action (the dispatching actor must be a repository +# admin, or a listed login). The app must be a bypass actor on the +# release-branch ruleset and on the `v*` tag ruleset. +# +# To settle by PR instead — a reviewed `chore/release-settle-vX.Y.Z` PR onto +# the release branch, tagged by release-publish.yml when it merges — remove +# `environment:`, `settle_mode` and `settlers` below. on: workflow_dispatch: @@ -18,8 +29,8 @@ on: required: true type: string -# One settle per version: two dispatches for the same version would both -# force-push the same settle branch. Queue, never cancel. +# One settle per version: two dispatches for the same version would race on +# the release branch. Queue, never cancel. concurrency: group: ${{ github.workflow }}-${{ inputs.version }} cancel-in-progress: false @@ -30,6 +41,7 @@ permissions: jobs: settle: runs-on: ubuntu-latest + environment: release steps: - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3 id: app-token @@ -50,8 +62,5 @@ jobs: version_file: VERSION version_pattern: plain changelog_file: CHANGELOG.md - # Settle without a PR — the dispatch is the decision, enforced by - # `settlers` (logins and/or `admin` = repository admins). The app - # must be a bypass actor on the release-branch ruleset. - # settle_mode: direct - # settlers: admin + settle_mode: direct + settlers: admin