|
| 1 | +# Single source of truth mapping (repo, branch) → (upload container, |
| 2 | +# read fallback chain) for Mathlib's multi-container cache. |
| 3 | +# |
| 4 | +# Called by build, upload_cache, and post_steps in build_template.yml so |
| 5 | +# trust classification is decided in exactly one place. Lean-side cache |
| 6 | +# logic stays branch-agnostic; this composite is the seam where CI-only |
| 7 | +# trust policy lives. |
| 8 | + |
| 9 | +name: Cache trust dispatch |
| 10 | +description: Compute the cache container target and read fallback for this job. |
| 11 | + |
| 12 | +inputs: |
| 13 | + repo: |
| 14 | + description: GitHub repo full name (`owner/name`). |
| 15 | + required: true |
| 16 | + branch: |
| 17 | + description: Branch name (`github.head_ref || github.ref_name`). |
| 18 | + required: true |
| 19 | + head-sha: |
| 20 | + description: | |
| 21 | + Head commit SHA for the ref being built. Used as the per-commit cache |
| 22 | + namespace (`MATHLIB_CACHE_REPO_SCOPE`) for fork-trust uploads, so a |
| 23 | + closed/hidden PR's poisoned artifacts cannot be served to a later |
| 24 | + honest PR from the same fork. |
| 25 | + required: true |
| 26 | + |
| 27 | +# Outputs are mirrored to $GITHUB_ENV inside the step, which is what |
| 28 | +# downstream `cache get` / `cache put-staged` calls actually read. We |
| 29 | +# also expose them as action outputs for callers that need them in |
| 30 | +# `with:` blocks (e.g. constructing further `if:` conditions). |
| 31 | +outputs: |
| 32 | + primary: |
| 33 | + description: Container name for uploads (master, forks, nightly-testing, pr-toolchain-tests). |
| 34 | + value: ${{ steps.dispatch.outputs.primary }} |
| 35 | + read-chain: |
| 36 | + description: | |
| 37 | + Comma-separated read fallback chain for `MATHLIB_CACHE_FROM`. Empty |
| 38 | + when the job should use the cache tool's repo-level default. |
| 39 | + value: ${{ steps.dispatch.outputs.read-chain }} |
| 40 | + repo-scope: |
| 41 | + description: | |
| 42 | + Per-commit namespace suffix for `MATHLIB_CACHE_REPO_SCOPE`. Set to |
| 43 | + the head SHA when uploading to fork-trust containers; empty for |
| 44 | + master / nightly / pr-toolchain-tests uploads where scoping isn't |
| 45 | + applied. |
| 46 | + value: ${{ steps.dispatch.outputs.repo-scope }} |
| 47 | + |
| 48 | +runs: |
| 49 | + using: composite |
| 50 | + steps: |
| 51 | + - name: Compute trust dispatch |
| 52 | + id: dispatch |
| 53 | + shell: bash |
| 54 | + run: | |
| 55 | + REPO="${{ inputs.repo }}" |
| 56 | + BRANCH="${{ inputs.branch }}" |
| 57 | + HEAD_SHA="${{ inputs.head-sha }}" |
| 58 | + PRIMARY="" |
| 59 | + READ_CHAIN="" |
| 60 | + REPO_SCOPE="" |
| 61 | +
|
| 62 | + # Security note: this dispatch is NOT the trust boundary for writes. |
| 63 | + # The real enforcement is the OIDC bearer token minted in upload_cache: |
| 64 | + # the token is scoped to a specific container, so a malicious actor |
| 65 | + # rewriting this case to `--container=master` from a fork build would |
| 66 | + # be 403'd by Azure regardless. The dispatch exists so the workflow |
| 67 | + # does the right thing in the honest case; defence in depth is RBAC. |
| 68 | +
|
| 69 | + case "$REPO" in |
| 70 | + "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 |
| 95 | + ;; |
| 96 | + "leanprover-community/mathlib4-nightly-testing") |
| 97 | + case "$BRANCH" in |
| 98 | + "nightly-testing"|"nightly-testing-green"|"staging"|bump/*) |
| 99 | + # Trusted nightly refs use the default [nightly-testing, legacy]. |
| 100 | + # It excludes `pr-toolchain-tests` so an upload from a |
| 101 | + # `lean-pr-testing-*` branch never reaches a trusted-nightly |
| 102 | + # consumer. |
| 103 | + PRIMARY="nightly-testing" |
| 104 | + ;; |
| 105 | + *) |
| 106 | + # `lean-pr-testing-*`, `batteries-pr-testing-*`, etc.: |
| 107 | + # least-trusted (can build with arbitrary toolchains). Widen |
| 108 | + # reads to recover this branch's own previously-uploaded |
| 109 | + # artifacts; trusted-nightly stays preferred where hash |
| 110 | + # spaces happen to align. |
| 111 | + PRIMARY="pr-toolchain-tests" |
| 112 | + READ_CHAIN="pr-toolchain-tests,nightly-testing,legacy" |
| 113 | + ;; |
| 114 | + esac |
| 115 | + ;; |
| 116 | + *) |
| 117 | + # Foreign fork. The cache tool's default chain for a fork repo |
| 118 | + # is [master, forks, legacy] (master-first): master supplies the |
| 119 | + # bulk of unchanged upstream deps, forks supplies PR-specific |
| 120 | + # files. No widening needed, so MATHLIB_CACHE_FROM stays unset. |
| 121 | + PRIMARY="forks" |
| 122 | + ;; |
| 123 | + esac |
| 124 | +
|
| 125 | + # Per-commit cache namespace, only for fork-trust uploads. Closes the |
| 126 | + # within-fork temporal replay attack: each commit's CI run gets its |
| 127 | + # own /f/{repo}/{sha}/... namespace, so artifacts from a closed/ |
| 128 | + # hidden PR cannot be served to a later honest build on the same |
| 129 | + # fork. Master / nightly / pr-toolchain-tests uploads stay un-scoped: |
| 130 | + # master has a single writer (no replay risk), and the per-toolchain |
| 131 | + # hash partitioning isolates nightly and toolchain-test classes via |
| 132 | + # their root-hash inputs. |
| 133 | + if [ "$PRIMARY" = "forks" ]; then |
| 134 | + REPO_SCOPE="$HEAD_SHA" |
| 135 | + fi |
| 136 | +
|
| 137 | + echo "primary=$PRIMARY" >> "$GITHUB_OUTPUT" |
| 138 | + echo "read-chain=$READ_CHAIN" >> "$GITHUB_OUTPUT" |
| 139 | + echo "repo-scope=$REPO_SCOPE" >> "$GITHUB_OUTPUT" |
| 140 | + echo "MATHLIB_CACHE_PRIMARY=$PRIMARY" >> "$GITHUB_ENV" |
| 141 | + if [ -n "$READ_CHAIN" ]; then |
| 142 | + echo "MATHLIB_CACHE_FROM=$READ_CHAIN" >> "$GITHUB_ENV" |
| 143 | + fi |
| 144 | + if [ -n "$REPO_SCOPE" ]; then |
| 145 | + echo "MATHLIB_CACHE_REPO_SCOPE=$REPO_SCOPE" >> "$GITHUB_ENV" |
| 146 | + fi |
| 147 | + # Visible in CI logs so a glance at any cache-touching step shows |
| 148 | + # what trust class the job is operating under. |
| 149 | + SCOPE_NOTE="" |
| 150 | + if [ -n "$REPO_SCOPE" ]; then |
| 151 | + SCOPE_NOTE=", MATHLIB_CACHE_REPO_SCOPE=$REPO_SCOPE" |
| 152 | + fi |
| 153 | + if [ -n "$READ_CHAIN" ]; then |
| 154 | + echo "cache-trust-dispatch: REPO=$REPO BRANCH=$BRANCH → container=$PRIMARY, MATHLIB_CACHE_FROM=$READ_CHAIN$SCOPE_NOTE" |
| 155 | + else |
| 156 | + echo "cache-trust-dispatch: REPO=$REPO BRANCH=$BRANCH → container=$PRIMARY, MATHLIB_CACHE_FROM=<default>$SCOPE_NOTE" |
| 157 | + fi |
0 commit comments