diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index baf16fe..84c32c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,23 +3,44 @@ name: CI on: push: branches: [main] + paths-ignore: + - '**.md' + - 'docs/**' + - 'LICENSE*' + - '.gitignore' + - '.editorconfig' pull_request: branches: [main] + paths-ignore: + - '**.md' + - 'docs/**' + - 'LICENSE*' + - '.gitignore' + - '.editorconfig' concurrency: - group: ci-${{ github.ref }} + group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Default to read-only token. Per-job escalation only where required. +permissions: + contents: read + jobs: lint: name: Lint & Format runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: '3.13' + # setup-python's pip cache hashes pyproject.toml. Massive + # warm-cache win on lint job since it only needs ruff. + cache: 'pip' + cache-dependency-path: pyproject.toml - name: Install ruff run: pip install ruff==0.15.4 @@ -33,6 +54,7 @@ jobs: test: name: Test (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest + timeout-minutes: 10 strategy: fail-fast: false matrix: @@ -43,6 +65,8 @@ jobs: - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: pyproject.toml - name: Install dependencies run: pip install -e ".[dev]" @@ -53,12 +77,15 @@ jobs: validate-api-contract: name: Validate API Contract runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: '3.13' + cache: 'pip' + cache-dependency-path: pyproject.toml - name: Install dependencies run: pip install -e ".[dev]" @@ -86,6 +113,7 @@ jobs: build: name: Build runs-on: ubuntu-latest + timeout-minutes: 5 needs: [lint, test] steps: - uses: actions/checkout@v6 @@ -93,6 +121,8 @@ jobs: - uses: actions/setup-python@v6 with: python-version: '3.13' + cache: 'pip' + cache-dependency-path: pyproject.toml - name: Install build tools run: pip install build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 29eb290..004cff9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,20 +5,31 @@ on: types: [published] workflow_dispatch: +# Default to read-only. id-token: write is escalated only on the +# publish job that actually needs PyPI Trusted Publishing OIDC. permissions: - id-token: write contents: read +# Only one publish at a time -- never two concurrent publishes of +# the same version. Don't cancel in progress: a half-rolled-back +# publish is worse than two queued. +concurrency: + group: release-pypi + cancel-in-progress: false + jobs: validate: name: Validate runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: '3.13' + cache: 'pip' + cache-dependency-path: pyproject.toml - name: Lint run: | @@ -39,13 +50,21 @@ jobs: publish: name: Publish to PyPI runs-on: ubuntu-latest + timeout-minutes: 10 needs: validate + # PyPI Trusted Publishing needs id-token: write to mint the OIDC + # token. contents: read is for checkout. Nothing else escalates. + permissions: + contents: read + id-token: write steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: '3.13' + cache: 'pip' + cache-dependency-path: pyproject.toml - name: Get version id: version diff --git a/.github/workflows/sync-from-api.yml b/.github/workflows/sync-from-api.yml index 09e30b2..7302f02 100644 --- a/.github/workflows/sync-from-api.yml +++ b/.github/workflows/sync-from-api.yml @@ -5,20 +5,32 @@ on: types: [openapi-spec-updated] workflow_dispatch: +# Workflow-level: minimal. Job-level escalates to write where needed. permissions: - contents: write - pull-requests: write + contents: read + +# Only one sync at a time -- two parallel runs would race on the +# branch + PR creation. +concurrency: + group: sync-types + cancel-in-progress: false jobs: sync-types: name: Generate Types from OpenAPI runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: '3.13' + cache: 'pip' + cache-dependency-path: pyproject.toml - name: Install dependencies run: pip install -e ".[dev]"