1- # Single source of truth mapping (repo, branch ) → (upload container,
1+ # Single source of truth mapping (repo, ref ) → (upload container,
22# read fallback chain) for Mathlib's multi-container cache.
33#
44# Called by build, upload_cache, and post_steps in build_template.yml so
@@ -14,7 +14,10 @@ inputs:
1414 description : GitHub repo full name (`owner/name`).
1515 required : true
1616 branch :
17- description : Branch name (`github.head_ref || github.ref_name`).
17+ description : |
18+ Branch name (`github.head_ref || github.ref_name`); on tag-triggered
19+ runs this is the tag name, distinguished via `github.ref_type` inside
20+ the dispatch step.
1821 required : true
1922 head-sha :
2023 description : |
5558 REPO="${{ inputs.repo }}"
5659 BRANCH="${{ inputs.branch }}"
5760 HEAD_SHA="${{ inputs.head-sha }}"
61+ # Whether `branch` names a branch or a tag. Read from the run context
62+ # rather than an input: unlike `repo`/`branch`, the value does not
63+ # depend on which event shape (PR vs push) the caller handles.
64+ REF_TYPE="${{ github.ref_type }}"
5865 PRIMARY=""
5966 READ_CHAIN=""
6067 REPO_SCOPE=""
@@ -66,32 +73,69 @@ runs:
6673 # be 403'd by Azure regardless. The dispatch exists so the workflow
6774 # does the right thing in the honest case; defence in depth is RBAC.
6875
76+ # Privileged containers (master, nightly-testing, pr-toolchain-tests) are
77+ # writable only by a repo's own native CI, whose OIDC token is RBAC-scoped
78+ # to match. A cross-repo pull request is built by build_fork.yml with
79+ # fork-trust credentials whatever repo the PR head lives on (e.g. a `bump/*`
80+ # branch on the mathlib4-nightly-testing fork), so it can only write
81+ # `forks`. `GITHUB_REPOSITORY` is the repo the workflow runs as (the base
82+ # repo for `pull_request_target`), i.e. the one whose credentials this job
83+ # holds; when it differs from `$REPO`, the build is a fork and targets
84+ # `forks` regardless of the head repo's own trust class.
85+ if [ "$REPO" != "$GITHUB_REPOSITORY" ]; then
86+ PRIMARY="forks"
87+ else
6988 case "$REPO" in
7089 "leanprover-community/mathlib4")
71- case "$BRANCH" in
72- "master"|"staging")
73- # Master / staging are the only writers that feed `master`
74- # (`staging` is bors's merge candidate, which fast-forwards to
75- # `master`). Read `master` only, not the default [master,
76- # legacy]: files the read chain serves are skipped at stage
77- # time, so keeping `legacy` would leave legacy-only files out of
78- # `master` for good. Reading `master` alone turns them into
79- # misses that get rebuilt and uploaded, so `master` fills itself
80- # into a standalone cache. (Only PRIMARY=master does this; other
81- # runs write to `forks` and keep the wider chain.)
82- PRIMARY="master"
83- READ_CHAIN="master"
84- ;;
85- *)
86- # `bors trying`, `ci-dev/*`, maintainer dev branches on the
87- # canonical repo: trust level is fork-equivalent (the OIDC
88- # token's RBAC scopes them to `forks`). Reads must widen
89- # past the default [master, legacy] so the post-build
90- # verification finds the just-uploaded fork-trust artifacts.
91- PRIMARY="forks"
92- READ_CHAIN="master,forks,legacy"
93- ;;
94- esac
90+ if [ "$REF_TYPE" = "tag" ]; then
91+ case "$BRANCH" in
92+ v4.*)
93+ # `v4.*` release tags: release_cache.yml rebuilds off-master
94+ # release commits (patch releases, patched release
95+ # candidates) and publishes them into `master`, the container
96+ # canonical checkouts read. Tag creation is restricted to
97+ # release managers by a tag ruleset, and the
98+ # `cache-upload-master` environment admits `v4.*` tag refs,
99+ # so these builds carry master trust. Reads are `master`-only
100+ # for the same fill-in reason as the master/staging arm
101+ # below.
102+ PRIMARY="master"
103+ READ_CHAIN="master"
104+ ;;
105+ *)
106+ # Other tags have no trust class of their own:
107+ # fork-equivalent, like the dev-branch arm below.
108+ PRIMARY="forks"
109+ READ_CHAIN="master,forks,legacy"
110+ ;;
111+ esac
112+ else
113+ case "$BRANCH" in
114+ "master"|"staging")
115+ # Master, staging, and `v4.*` release tags (above) are the
116+ # only writers that feed `master` (`staging` is bors's merge
117+ # candidate, which fast-forwards to `master`). Read `master`
118+ # only, not the default [master, legacy]: files the read
119+ # chain serves are skipped at stage time, so keeping `legacy`
120+ # would leave legacy-only files out of `master` for good.
121+ # Reading `master` alone turns them into misses that get
122+ # rebuilt and uploaded, so `master` fills itself into a
123+ # standalone cache. (Only PRIMARY=master does this; other
124+ # runs write to `forks` and keep the wider chain.)
125+ PRIMARY="master"
126+ READ_CHAIN="master"
127+ ;;
128+ *)
129+ # `bors trying`, `ci-dev/*`, maintainer dev branches on the
130+ # canonical repo: trust level is fork-equivalent (the OIDC
131+ # token's RBAC scopes them to `forks`). Reads must widen
132+ # past the default [master, legacy] so the post-build
133+ # verification finds the just-uploaded fork-trust artifacts.
134+ PRIMARY="forks"
135+ READ_CHAIN="master,forks,legacy"
136+ ;;
137+ esac
138+ fi
95139 ;;
96140 "leanprover-community/mathlib4-nightly-testing")
97141 case "$BRANCH" in
@@ -121,6 +165,7 @@ runs:
121165 PRIMARY="forks"
122166 ;;
123167 esac
168+ fi
124169
125170 # Per-commit cache namespace, only for fork-trust uploads. Closes the
126171 # within-fork temporal replay attack: each commit's CI run gets its
0 commit comments