ci: gate PRs on 100% patch coverage + 95% project floor #68
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [master] | |
| # CI-minute savings (2026-05-21): skip CI on docs-only commits. | |
| paths-ignore: | |
| - '**.md' | |
| - 'docs/**' | |
| - 'CLAUDE.md' | |
| - '.gitignore' | |
| - 'LICENSE' | |
| - 'BUGBASH-*/**' | |
| pull_request: | |
| branches: [master] | |
| paths-ignore: | |
| - '**.md' | |
| - 'docs/**' | |
| - 'CLAUDE.md' | |
| - '.gitignore' | |
| - 'LICENSE' | |
| - 'BUGBASH-*/**' | |
| concurrency: | |
| # CI-minute savings (2026-05-21): cancel prior in-flight CI for the same | |
| # branch/PR when a new commit lands. | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Stale-green guard. A PR can show a green CI run that was executed BEFORE a | |
| # breaking commit landed on the base branch — merging it would ship a broken | |
| # master. This job FAILS if the PR branch does not contain origin/<base> as | |
| # an ancestor, forcing an "Update branch" before the PR can merge. | |
| up-to-date-with-base: | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Fail if PR branch is behind its base branch | |
| run: | | |
| BASE="${{ github.event.pull_request.base.ref }}" | |
| git fetch origin "${BASE}" --depth=1 | |
| if git merge-base --is-ancestor "origin/${BASE}" HEAD; then | |
| echo "PR branch contains origin/${BASE} — up to date." | |
| else | |
| echo "::error::PR branch is behind origin/${BASE}. Update the branch (merge/rebase ${BASE}) and re-run CI so it validates against current base." | |
| exit 1 | |
| fi | |
| build-and-test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Checkout proto sibling (replace ../proto) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ vars.PROTO_REPO || format('{0}/proto', github.repository_owner) }} | |
| # REPO_ACCESS_TOKEN is a fine-grained PAT with read on the private | |
| # sibling repos; GITHUB_TOKEN is scoped to THIS repo only and 404s. | |
| token: ${{ secrets.REPO_ACCESS_TOKEN }} | |
| path: _proto_ci | |
| - run: mv _proto_ci ../proto | |
| - name: Checkout common sibling (replace ../common) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ vars.COMMON_REPO || format('{0}/common', github.repository_owner) }} | |
| token: ${{ secrets.REPO_ACCESS_TOKEN }} | |
| path: _common_ci | |
| - run: mv _common_ci ../common | |
| # Cross-repo registry-iterating tests (CLAUDE.md rule 18) text-walk | |
| # api/internal/models/audit_kinds.go and api/e2e/reliability_contract_test.go | |
| # to assert worker auditKind* wire values match the api source. Without | |
| # this checkout the tests SKIP in CI, leaving cross-repo drift detection | |
| # to developer machines only. INSTANT_API_REPO is set on the test step | |
| # below so findApiRepoRoot() locates the sibling deterministically. | |
| - name: Checkout api sibling (for cross-repo registry-iterating tests) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: ${{ vars.API_REPO || format('{0}/api', github.repository_owner) }} | |
| token: ${{ secrets.REPO_ACCESS_TOKEN }} | |
| path: _api_ci | |
| fetch-depth: 1 | |
| - run: mv _api_ci ../api | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.24' | |
| - run: go build ./... | |
| - run: go vet ./... | |
| - name: Run tests (with INSTANT_API_REPO for cross-repo gate) | |
| env: | |
| INSTANT_API_REPO: ${{ github.workspace }}/../api | |
| run: go test ./... -v -race -count=1 |