1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# The required `release / gate` check.
#
# Always runs, with no top-level path filters, so that a required status check
# can never be satisfied by being skipped. Change impact is computed inside the
# workflow instead.
name: release
on:
pull_request:
merge_group:
permissions:
concurrency:
group: release-ci-${{ github.ref }}
cancel-in-progress: true
jobs:
impact:
name: change impact
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
full: ${{ steps.classify.outputs.full }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
persist-credentials: false
# Tiered rather than binary: ordinary dependency work should not pay for
# the full closure build, but anything that changes what gets published,
# or how, must.
- name: Classify
id: classify
env:
BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
HEAD: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }}
run: |
set -euo pipefail
changed=$(git diff --name-only "${BASE}" "${HEAD}")
echo "changed files:"; echo "${changed}"
full=false
while read -r file; do
case "${file}" in
.release/*|Makefile.toml|.github/workflows/*|Cargo.lock|*/Cargo.toml|Cargo.toml)
full=true ;;
esac
done <<< "${changed}"
echo "full=${full}" >> "${GITHUB_OUTPUT}"
echo "full closure verification: ${full}"
verify:
name: verify
needs: impact
# A called workflow can only *downgrade* the calling job's token, so this
# grant is the ceiling for every job in `release-verify.yml`. Without it the
# job inherits the top-level `{}` and the call is rejected as an escalation
# when the run graph is built -- which fails the whole run before any job
# starts, rather than failing this one.
permissions:
contents: read
uses: ./.github/workflows/release-verify.yml
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
full: ${{ needs.impact.outputs.full == 'true' }}
gate:
name: gate
# The single required check. Runs unconditionally and fails if any required
# job failed *or was unexpectedly skipped*, so that a misconfigured
# dependency cannot turn a red build green.
if: always()
needs:
runs-on: ubuntu-latest
steps:
- name: Aggregate
# Results arrive through the environment rather than by interpolation
# into the script, which is the rule regardless of whether a particular
# value could carry anything interesting.
env:
IMPACT: ${{ needs.impact.result }}
VERIFY: ${{ needs.verify.result }}
run: |
set -euo pipefail
echo "impact: ${IMPACT}"
echo "verify: ${VERIFY}"
for result in "${IMPACT}" "${VERIFY}"; do
case "${result}" in
success) ;;
*) echo "a required release check did not succeed" >&2; exit 1 ;;
esac
done
echo "release gate: ok"