From 19ec901486b8deed823bd6b890fdd46a8c62cc50 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Tue, 9 Jun 2026 17:22:16 -0700 Subject: [PATCH 1/3] Give each workflow gate job a unique check name. --- .github/workflows/build.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb673da..0d3e29f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,7 +59,7 @@ jobs: ./scripts/repro_test.py \ --image "${{ steps.pair.outputs.image }}" - complete: + build-complete: if: always() needs: - smoke-build diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 12a7790..20095ec 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -77,7 +77,7 @@ jobs: - name: run pytest run: uv run pytest - complete: + lint-complete: if: always() needs: - json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5996a76..32f22ec 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -293,7 +293,7 @@ jobs: release-artifacts/sbom-*.spdx.json release-artifacts/prov-*.intoto.jsonl - complete: + publish-complete: if: always() needs: - matrix diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d8b4f2a..3c2a94b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,7 +83,7 @@ jobs: --title "$title" \ --body "$body" - complete: + release-complete: if: always() needs: - prepare From 5a9ccc97565785c5eec287900df8c9d5549fa94f Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Tue, 9 Jun 2026 17:56:50 -0700 Subject: [PATCH 2/3] Document per-workflow gate check names. --- RELEASE.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index a6a8529..6376747 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -42,12 +42,12 @@ These are set in the workflow YAML, not in repo settings — but worth knowing w ### Branch protection -The `complete` job in each workflow (`lint.yml`, `build.yml`, `publish.yml`, `release.yml`) is a single aggregator status check. Configure branch protection on `main` to require these checks before merging: +Each workflow has a single aggregator status check, named uniquely per workflow so they can be required independently: `lint-complete` (`lint.yml`), `build-complete` (`build.yml`), `publish-complete` (`publish.yml`), and `release-complete` (`release.yml`). Configure branch protection on `main` to require both PR checks before merging: -- `lint / complete` -- `build / complete` +- `lint-complete` +- `build-complete` -The `publish` and `release` workflows fire on release events / dispatch and don't gate merges to `main`. +Require both, not just one — a single required check that happens to finish last (e.g. the slower build) won't catch a failure in the other workflow, and auto-merge can fire before that workflow reports. The `publish` and `release` workflows fire on release events / dispatch and don't gate merges to `main`. ## Release tag scheme @@ -127,7 +127,7 @@ Triggered exclusively by the `release: published` event — when a maintainer cl | `manifest` | Assembles the multi-arch manifest list `:-rust` per rust base. Lists are (re)created via `docker buildx imagetools create`, overwriting any existing list. | | `aliases` | Re-points `:` to the manifest list of `(cli, default rust pin)` — the highest `rust_versions[]` pin whose label matches `default_distro`, newest digest winning a tie. If this cli is the newest declared, also re-points `:latest`. Both tags are intentionally moving; the job fails loudly if no `rust_versions[]` pin matches `default_distro`. | | `release` | Downloads every per-arch metadata + (when present) SBOM/provenance artifact, calls `scripts/release_body.py` to compose a structural body section, then **appends** that section to the just-created release body and attaches the SBOM + provenance files for freshly-built pairs as release assets. Any human-written notes already in the release body are preserved. | -| `complete` | Branch-protection aggregator. Fails if any upstream job failed or was cancelled. | +| `publish-complete` | Branch-protection aggregator. Fails if any upstream job failed or was cancelled. | ## Mutable tags and restarts From f831cb35ed50d95c0202ca269a0a74e70b22b810 Mon Sep 17 00:00:00 2001 From: Nando Vieira Date: Tue, 9 Jun 2026 19:17:45 -0700 Subject: [PATCH 3/3] Gate PRs through a single ci.yml workflow. --- .github/workflows/build.yml | 19 +------------------ .github/workflows/ci.yml | 31 +++++++++++++++++++++++++++++++ .github/workflows/lint.yml | 19 +------------------ .github/workflows/publish.yml | 2 +- .github/workflows/release.yml | 2 +- RELEASE.md | 9 ++++----- 6 files changed, 39 insertions(+), 43 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0d3e29f..9ff1a1f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,18 +2,11 @@ name: build on: - pull_request: - push: - branches: - - main + workflow_call: permissions: contents: read -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - jobs: smoke-build: name: build & smoke-test newest image @@ -58,13 +51,3 @@ jobs: run: | ./scripts/repro_test.py \ --image "${{ steps.pair.outputs.image }}" - - build-complete: - if: always() - needs: - - smoke-build - runs-on: ubuntu-24.04 - steps: - - name: check upstream jobs - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') - run: exit 1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1892a1e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +--- +name: ci + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + lint: + uses: ./.github/workflows/lint.yml + + build: + uses: ./.github/workflows/build.yml + + complete: + if: always() + name: complete + needs: [lint, build] + runs-on: ubuntu-24.04 + steps: + - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 20095ec..f8c3936 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,10 +2,7 @@ name: lint on: - pull_request: - push: - branches: - - main + workflow_call: permissions: contents: read @@ -76,17 +73,3 @@ jobs: enable-cache: true - name: run pytest run: uv run pytest - - lint-complete: - if: always() - needs: - - json - - dockerfile - - python - - matrix-smoke - - tests - runs-on: ubuntu-24.04 - steps: - - name: check upstream jobs - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') - run: exit 1 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 32f22ec..5996a76 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -293,7 +293,7 @@ jobs: release-artifacts/sbom-*.spdx.json release-artifacts/prov-*.intoto.jsonl - publish-complete: + complete: if: always() needs: - matrix diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3c2a94b..d8b4f2a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,7 +83,7 @@ jobs: --title "$title" \ --body "$body" - release-complete: + complete: if: always() needs: - prepare diff --git a/RELEASE.md b/RELEASE.md index 6376747..a3a5890 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -42,12 +42,11 @@ These are set in the workflow YAML, not in repo settings — but worth knowing w ### Branch protection -Each workflow has a single aggregator status check, named uniquely per workflow so they can be required independently: `lint-complete` (`lint.yml`), `build-complete` (`build.yml`), `publish-complete` (`publish.yml`), and `release-complete` (`release.yml`). Configure branch protection on `main` to require both PR checks before merging: +`ci.yml` is the single PR gate. It runs on `pull_request` (and pushes to `main`), calls `lint.yml` and `build.yml` as reusable workflows, and rolls them up into one `complete` job that `needs` both. Configure branch protection on `main` to require that one check before merging: -- `lint-complete` -- `build-complete` +- `complete` -Require both, not just one — a single required check that happens to finish last (e.g. the slower build) won't catch a failure in the other workflow, and auto-merge can fire before that workflow reports. The `publish` and `release` workflows fire on release events / dispatch and don't gate merges to `main`. +Because `complete` `needs` lint and build, the check can't report success until both finish — so auto-merge waits on all of CI through a single required check. The `publish` and `release` workflows fire on release events / dispatch and don't gate merges to `main`. ## Release tag scheme @@ -127,7 +126,7 @@ Triggered exclusively by the `release: published` event — when a maintainer cl | `manifest` | Assembles the multi-arch manifest list `:-rust` per rust base. Lists are (re)created via `docker buildx imagetools create`, overwriting any existing list. | | `aliases` | Re-points `:` to the manifest list of `(cli, default rust pin)` — the highest `rust_versions[]` pin whose label matches `default_distro`, newest digest winning a tie. If this cli is the newest declared, also re-points `:latest`. Both tags are intentionally moving; the job fails loudly if no `rust_versions[]` pin matches `default_distro`. | | `release` | Downloads every per-arch metadata + (when present) SBOM/provenance artifact, calls `scripts/release_body.py` to compose a structural body section, then **appends** that section to the just-created release body and attaches the SBOM + provenance files for freshly-built pairs as release assets. Any human-written notes already in the release body are preserved. | -| `publish-complete` | Branch-protection aggregator. Fails if any upstream job failed or was cancelled. | +| `complete` | Single aggregator for the publish workflow. Fails if any upstream job failed or was cancelled. | ## Mutable tags and restarts