From 714a153315a61cf2b3270b6b8ef99f1eb4e68b4d Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 10 May 2026 21:27:32 +0800 Subject: [PATCH] ci(gates): protect main with quality gates Signed-off-by: Ivan Li --- .github/quality-gates.json | 45 +++++++++++++++++++ .github/workflows/build.yml | 8 ++-- .github/workflows/release.yml | 42 ++++++++++------- README.md | 4 ++ docs/specs/README.md | 5 +++ .../specs/b7k2m-repo-quality-gates/HISTORY.md | 9 ++++ .../IMPLEMENTATION.md | 18 ++++++++ docs/specs/b7k2m-repo-quality-gates/SPEC.md | 32 +++++++++++++ 8 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 .github/quality-gates.json create mode 100644 docs/specs/README.md create mode 100644 docs/specs/b7k2m-repo-quality-gates/HISTORY.md create mode 100644 docs/specs/b7k2m-repo-quality-gates/IMPLEMENTATION.md create mode 100644 docs/specs/b7k2m-repo-quality-gates/SPEC.md diff --git a/.github/quality-gates.json b/.github/quality-gates.json new file mode 100644 index 0000000..28ce820 --- /dev/null +++ b/.github/quality-gates.json @@ -0,0 +1,45 @@ +{ + "schema_version": 1, + "policy": { + "baseline_policy": "explicit-waiver-required", + "require_signed_commits": true, + "branch_protection": { + "protected_branches": [ + "main" + ], + "require_pull_request": true, + "disallow_direct_pushes": true + }, + "review_policy": { + "mode": "conditional-required", + "required_approvals": 1, + "exempt_repository_owner": true, + "exempt_author_permissions": [ + "admin", + "maintain" + ], + "allowed_reviewer_permissions": [ + "write", + "maintain", + "admin" + ], + "enforcement": { + "mode": "github-native", + "bypass_mode": "pull-request-only" + } + } + }, + "required_checks": [ + "Build" + ], + "informational_checks": [], + "waivers": [], + "expected_pr_workflows": [ + { + "workflow": "Build", + "jobs": [ + "Build" + ] + } + ] +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 263262e..296cd4e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,18 +5,20 @@ on: branches: [ "main" ] pull_request: branches: [ "main" ] + merge_group: env: CARGO_TERM_COLOR: always jobs: build: + name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Build + - name: Build default features run: cargo build --verbose - name: Check documentation examples run: | @@ -24,5 +26,5 @@ jobs: # Skip actual doc tests due to embedded target configuration echo "Checking documentation examples compilation..." cargo check --verbose - - name: Build - run: cargo build --verbose --features async \ No newline at end of file + - name: Build async feature + run: cargo build --verbose --features async diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 72f87c4..2566e90 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,30 +4,40 @@ on: workflow_dispatch: inputs: version: - description: "major/minor/patch or semver" + description: "Expected Cargo.toml version" required: false - default: "patch" concurrency: release-crate +permissions: + contents: write + jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Set git credentials + - name: Verify requested version + id: crate + env: + EXPECTED_VERSION: ${{ github.event.inputs.version }} run: | - git config user.name github-actions - git config user.email github-actions@github.com - - name: Crates publish - uses: kaleidawave/crates-release-gh-action@main - id: release - with: - version: ${{ github.event.inputs.version }} - crates-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} - - name: Push updated Cargo.toml + actual="$( + cargo metadata --no-deps --format-version 1 \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["packages"][0]["version"])' + )" + if [ -n "${EXPECTED_VERSION}" ] && [ "${EXPECTED_VERSION}" != "${actual}" ]; then + echo "Expected version ${EXPECTED_VERSION}, but Cargo.toml declares ${actual}." >&2 + exit 1 + fi + echo "version=${actual}" >> "${GITHUB_OUTPUT}" + - name: Publish crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --locked + - name: Push release tag + env: + version: ${{ steps.crate.outputs.version }} run: | - git add . - git commit -m "Release: ${{ steps.release.outputs.new-versions-description }}" - git tag "release/${{ steps.release.outputs.new-version }}" - git push --tags origin main \ No newline at end of file + git tag "release/${version}" + git push origin "release/${version}" diff --git a/README.md b/README.md index 19634d0..947a22a 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,10 @@ where I2C::Error: core::fmt::Debug - `async`: Enables async/await support for non-blocking operations - `defmt`: Enables structured logging with defmt +## Maintainer Quality Gates + +Changes to `main` must go through a pull request with the `Build` GitHub check passing. The repository-local quality gate contract is recorded in `.github/quality-gates.json`; GitHub branch protection should require the same `Build` check and block direct pushes to `main`, including release version updates. + ## Hardware Support This driver is designed for the SW2303 USB PD controller. The SW2303 is a USB PD controller that supports: diff --git a/docs/specs/README.md b/docs/specs/README.md new file mode 100644 index 0000000..036a6c0 --- /dev/null +++ b/docs/specs/README.md @@ -0,0 +1,5 @@ +# Specs + +| ID | Topic | Status | Spec | Summary | +| --- | --- | --- | --- | --- | +| b7k2m | Repository quality gates | Active | [b7k2m-repo-quality-gates/SPEC.md](b7k2m-repo-quality-gates/SPEC.md) | Protect `main` with PR-only `Build` gate and signed commits. | diff --git a/docs/specs/b7k2m-repo-quality-gates/HISTORY.md b/docs/specs/b7k2m-repo-quality-gates/HISTORY.md new file mode 100644 index 0000000..a099975 --- /dev/null +++ b/docs/specs/b7k2m-repo-quality-gates/HISTORY.md @@ -0,0 +1,9 @@ +# History + +## Origin + +PR #1 merged while `main` had no GitHub branch protection and no repository rulesets. The repository needed a repo-local declaration plus GitHub-native enforcement so future merges cannot depend on operator memory. + +## Decision + +Use a single required check named `Build`, require pull requests for `main`, block direct pushes, and require signed commits. Keep `.github/quality-gates.json` as the source of truth and treat GitHub protection drift as a reportable failure. diff --git a/docs/specs/b7k2m-repo-quality-gates/IMPLEMENTATION.md b/docs/specs/b7k2m-repo-quality-gates/IMPLEMENTATION.md new file mode 100644 index 0000000..cf2bc20 --- /dev/null +++ b/docs/specs/b7k2m-repo-quality-gates/IMPLEMENTATION.md @@ -0,0 +1,18 @@ +# Implementation + +## Current Shape + +- `.github/quality-gates.json` declares the repo-local gate contract for `main`. +- `.github/workflows/build.yml` exposes a stable required check named `Build`. +- `.github/workflows/release.yml` publishes the signed version already present on `main` and pushes only the release tag. +- `README.md` documents the maintainer-facing quality gate. +- GitHub branch protection is aligned to the declaration after the required check exists on the pull request. + +## Validation + +- Parse `.github/quality-gates.json` as JSON. +- Run the style-playbook quality gate checker against the local declaration. +- Run `cargo fmt -- --check`. +- Run `cargo clippy --all-targets --all-features -- -D warnings`. +- Run `cargo test`. +- Verify GitHub protection state through `gh api` after remote alignment. diff --git a/docs/specs/b7k2m-repo-quality-gates/SPEC.md b/docs/specs/b7k2m-repo-quality-gates/SPEC.md new file mode 100644 index 0000000..b0784bb --- /dev/null +++ b/docs/specs/b7k2m-repo-quality-gates/SPEC.md @@ -0,0 +1,32 @@ +# Repository Quality Gates + +Spec ID: b7k2m + +## Problem + +The default branch accepted PR #1 because GitHub reported no branch protection for `main` and no repository rulesets. The only completed status check was `build`, so GitHub had no required review, signed-commit, direct-push, or required-check policy to enforce. + +## Requirements + +- `main` is the protected default branch. +- All changes to `main` enter through pull requests. +- Direct pushes to `main` are blocked. +- Signed commits are required for protected-branch changes. +- The required GitHub status check is `Build`, declared repo-locally in `.github/quality-gates.json`. +- The `Build` workflow supports `pull_request`, `push` to `main`, and `merge_group` events so branch protection and merge queue style checks can consume the same required context. +- Native GitHub branch protection or rulesets enforce the declared policy. Workflow-only checks or README text do not count as enforcement. +- Release automation must not push version metadata directly to `main`; protected-branch version updates enter through signed maintainer PRs before publishing. +- Any intentional divergence between `.github/quality-gates.json` and GitHub protection state must be recorded as a waiver or reported as drift. + +## Non-Goals + +- This crate does not use PR label release intent. No `Label Gate` check is required until release intent is driven by PR labels. +- This spec does not change Rust driver behavior or public API. + +## Acceptance + +- `.github/quality-gates.json` validates with the style-playbook quality gate checker. +- GitHub branch protection for `main` requires PRs, blocks direct pushes, requires signed commits, and requires the `Build` check. +- A PR changing the repository must show `Build` passing before merge. +- The README points maintainers to the repo-local quality gate contract. +- The release workflow publishes from the signed version already present on `main` and does not create protected-branch commits.