diff --git a/.github/workflows/build-release-task.yml b/.github/workflows/build-release-task.yml index a2278bfc..d49b927e 100644 --- a/.github/workflows/build-release-task.yml +++ b/.github/workflows/build-release-task.yml @@ -231,11 +231,10 @@ jobs: if: ${{ inputs.enable_nuget }} needs: [get-version, validate-release] runs-on: ubuntu-latest - # OIDC trusted publishing through NuGet/login needs id-token: write, granted by the caller on its call to this task. - # It is declared alongside contents: read, since a job-level permissions: block zeroes every unlisted scope, and this job's checkout steps need it. - permissions: - contents: read - id-token: write + # OIDC trusted publishing through NuGet/login needs id-token: write, and the caller grants it on its call to this task when it sets nuget: true, a real push. + # A build-only run (enable_nuget without nuget, or any smoke build) never reaches NuGet/login and needs no such grant. + # No job-level permissions: block here, because a called job's block is validated against the caller's grant before its if: runs. + # A block naming id-token would fail every caller that does not grant it at startup, including a smoke build under a read-only pull request token. steps: - name: Checkout code step @@ -534,10 +533,9 @@ jobs: if: ${{ inputs.github && !inputs.smoke && !failure() && !cancelled() }} runs-on: ubuntu-latest needs: [get-version, validate-release, build-executable, build-nuget, build-pypi, build-docker] - # The release upload and the artifact-delete cleanup both write with GITHUB_TOKEN, and the caller grants these. - permissions: - contents: write - actions: write + # The release upload and the artifact-delete cleanup both write with GITHUB_TOKEN, and the caller grants contents: write and actions: write when it sets github: true on a non-smoke run. + # A caller publishing only to a registry, or smoke building, leaves this job disabled and grants neither. + # No job-level permissions: block here, for the reason build-nuget gives: a block is validated against the caller's grant before if: runs, and a smoke caller holds a read-only token. steps: diff --git a/.github/workflows/validate-task.yml b/.github/workflows/validate-task.yml index 361ce5d9..bcc35773 100644 --- a/.github/workflows/validate-task.yml +++ b/.github/workflows/validate-task.yml @@ -116,7 +116,7 @@ jobs: # This is #729, decided here, the one place the fleet's uvx tools are pinned or floated. - name: Setup uv step if: hashFiles('pyproject.toml') != '' - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0 with: python-version: "3.13" @@ -230,7 +230,7 @@ jobs: - name: Setup uv step if: hashFiles('pyproject.toml') != '' && hashFiles('tests/**') != '' && hashFiles('uv.lock') != '' - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0 with: python-version: "3.13" diff --git a/docs/reusable-workflows.md b/docs/reusable-workflows.md index e77e709b..46c4cfc5 100644 --- a/docs/reusable-workflows.md +++ b/docs/reusable-workflows.md @@ -420,7 +420,7 @@ A repo whose publisher needs the release-gate decision reaches `publish-plan-tas ## Adopting the Release Chain -A downstream repo replaces the whole of its `.github/workflows/build-release-task.yml`, and every per-target leaf task it carries (`build-executable-task.yml`, `build-nugetlibrary-task.yml`, `build-pypilibrary-task.yml`, `build-docker-task.yml`), with a caller stub in its own `publish-release.yml` reaching the hub tasks by pin. `test-pull-request.yml`'s smoke job calls `build-release-task.yml` the same way, with `smoke: true` and the paths-filter's `enable_*` outputs. Nothing here lands as a catalog snippet in this pull request, since a caller stub's pin can only name a released hub commit and no release yet carries `build-release-task.yml` ([Pinning][pinning]). The snippet follows the release that first ships it, tracked in the [Rollout][rollout] section below. +A downstream repo replaces the whole of its `.github/workflows/build-release-task.yml`, and every per-target leaf task it carries (`build-executable-task.yml`, `build-nugetlibrary-task.yml`, `build-pypilibrary-task.yml`, `build-docker-task.yml`), with a caller stub in its own `publish-release.yml` reaching the hub tasks by pin. `test-pull-request.yml`'s smoke job calls `build-release-task.yml` the same way, with `smoke: true` and the paths-filter's `enable_*` outputs. Nothing here lands as a catalog snippet in this pull request, since a caller stub's pin can only name a released hub commit and no release yet carries `build-release-task.yml` ([Pinning][pinning]). The snippet follows the release that first ships it, tracked in the [Rollout][rollout] section below. The task declares no job-level `permissions:` of its own, because a called job's block is validated against the caller's grant before its `if:` runs and would fail a caller that does not grant it at startup. The caller therefore grants only what its enabled paths write with: `contents: write` and `actions: write` when it sets `github: true` on a non-smoke run (the release upload and the artifact cleanup), `id-token: write` when it sets `nuget: true` (a real push through `NuGet/login`), and nothing beyond `contents: read` on a build-only or smoke run, where a Dependabot pull request holds a read-only token. The stub keeps its own trigger policy exactly as today: `workflow_dispatch` plus a main-only weekly `schedule` for a Docker repo, or `workflow_dispatch` plus a paths-filtered `push` to `main` for a NuGet or PyPI repo whose merges should auto-publish. What moves to the hub is the release-gate decision, the build/version/publish job graph, and the Docker core, never the trigger. This is the full shape, a NuGet-library repo whose merges publish: