Repository navigation
172 lines (161 loc) · 8.54 KB
/
Copy pathpack-smoke-optin.yml
File metadata and controls
172 lines (161 loc) · 8.54 KB
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# Opt-in pre-merge pack smoke (#14214, derived from the #14000 ruling).
#
# ## The gap this closes
#
# `publish-smoke.yml` owns the pack smoke, and it runs on `workflow_run` AFTER a
# Release run — it resolves the changesets release branch and reports its verdict
# as a commit status on that branch head. That is the right place for a release
# verdict and the wrong place for a PR author: #11767 (audience default flipped
# to `invite_only`, a breaking auth change) merged with ZERO packed-install
# coverage on its own PR, and the smoke then sat red on the release candidate for
# ~7 days because the only surface carrying the verdict was one no PR author
# looks at (measured in #14000).
#
# This workflow adds the missing TRIGGER — and only the trigger. The driver, the
# probes and every assertion stay exactly as `scripts/publish-smoke.sh` defines
# them; re-pinning those to the declared first-run contract was #14000's own
# deliverable (PR #14255) and is deliberately untouched here. What is new is
# WHEN the smoke runs and WHERE its verdict lands: on the pull request, before
# the merge, as an ordinary check run the author already reads.
#
# ## Opt-in, by ruling — and what that buys and costs
#
# Maintainer ruling 2026-09-01 (director batch #23, 「同意」) approved the
# LABEL-GATED OPT-IN shape: the cost stays in opt-in. A full build + pack +
# clean install is ~45 minutes, so putting it in the default inner loop would
# tax every PR in the repo to cover the handful that can break a fresh install.
#
# ⛔ Therefore this workflow must never grow a `paths:` filter that runs it by
# default, and must never be added to `.github/labeler.yml`. Auto-applying the
# label from changed paths would be a DIFFERENT design (auto-detect), not the
# one that was ruled, and it would reintroduce the default-inner-loop cost the
# ruling removed.
#
# The residual risk is stated rather than hidden: self-declaration only covers
# the author who RECOGNISES their change as breaking. An author who does not
# realise they widened the unauthenticated surface still gets no pre-merge pack
# coverage — the release-time leg in `publish-smoke.yml` remains the backstop for
# that case. Closing that gap would require a detection rule, which is a
# different card and a different ruling.
#
# ## When to apply the label
#
# Apply `needs:pack-smoke` to your own PR when it changes the auth/audience
# DEFAULTS or the accept/reject behaviour of the unauthenticated surface. The
# same criterion is written where PR authors meet it, in CONTRIBUTING.md.
#
# ## Wiring decisions
#
# `types:` restates GitHub's three defaults alongside `labeled`, because naming
# `types:` REPLACES the default set rather than extending it (#8304) — dropping
# one produces no run on that activity, which is an absence rather than a skip.
# `opened` is in the list so a PR created via the API already carrying the label
# is smoked; `synchronize` is the load-bearing one, because the thing under test
# is the MERGE PREVIEW and every push changes it.
#
# The guard has two limbs and both are needed:
#
# 1. the PR must CURRENTLY carry the label — this is what makes an unlabelled
# PR cost nothing;
# 2. on a `labeled` event the label just added must be OURS. Without this limb
# every unrelated label the size labeler adds (`size/l`, `ci/cd`, …) to an
# already-labelled PR would start another 45-minute smoke of a tree that
# has not changed.
#
# `concurrency` sits at JOB level, not workflow level, and that is deliberate.
# At workflow level the group is taken by every run this workflow starts,
# including the runs whose job then skips — so an unrelated `labeled` event
# would CANCEL a smoke that was still running and then skip, leaving the PR with
# no verdict at all. That is the #14000 silence shape (a missing answer reading
# as a green one) rebuilt inside its own fix. A skipped job never enters a
# job-level group, so only a real smoke can supersede a real smoke.
#
# ⛔ No `merge_group:` trigger, and this is not the oversight it resembles. A
# `merge_group` event carries no `pull_request` context, so neither limb of the
# guard above can be evaluated there; and the ruling puts this cost pre-merge and
# opt-in, not in the queue. This job is correspondingly NOT a required context
# (`scripts/check-required-contexts.mjs` holds that registry) — it is advisory by
# design, and a PR that never opts in simply never produces it.
name: Pack Smoke (opt-in)
on:
pull_request:
branches:
- main
types: [opened, synchronize, reopened, labeled]
permissions:
contents: read
# A newer run for the same pull request supersedes this one, so the older run is
# cancelled. The group key is ci.yml's shape; `cancel-in-progress` is true only on
# `pull_request`, so no other event's run is ever cancelled. An unrelated label's run
# gets a group of its own (its job is skipped anyway), so adding some other label never
# cancels a running `needs:pack-smoke` run.
concurrency:
group: pack-smoke-optin-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.after || github.ref }}-${{ github.event.action == 'labeled' && github.event.label.name != 'needs:pack-smoke' && github.run_id || 'run' }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
pack-smoke:
# ~45 minutes of build + pack + clean install. Opt-in only — see the header.
name: Packed-tarball smoke (opt-in)
if: >-
contains(github.event.pull_request.labels.*.name, 'needs:pack-smoke')
&& (github.event.action != 'labeled' || github.event.label.name == 'needs:pack-smoke')
runs-on: ubuntu-latest
timeout-minutes: 45
concurrency:
group: pack-smoke-optin-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
# No `ref:` — on `pull_request` the default checkout is `refs/pull/N/merge`,
# the MERGE PREVIEW, which is the tree the ruling names: what `main` will
# actually contain, not what the branch contains in isolation. The driver
# packs from the checkout root (`REPO_ROOT` in scripts/publish-smoke.sh),
# so the preview is what gets packed, installed and probed.
- name: Checkout the merge preview
uses: actions/checkout@v7
- name: Setup workspace
uses: ./.github/actions/setup-workspace
# Restore here, save at the end of the job: the save sits behind the
# prune step below, which a combined `actions/cache` post-step cannot.
- name: Setup turbo cache
id: turbo-cache
uses: actions/cache/restore@v6
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.job }}-
${{ runner.os }}-turbo-
# The driver's own prerequisite, asserted by the script itself: pack mode
# fails fast unless packages/cli/dist and the create-objectstack bin exist.
# `--summarize` writes the run summary the prune step below keeps.
- name: Build
run: pnpm run build --summarize
# SMOKE_MODE defaults to `pack`, so this is the same invocation
# publish-smoke.yml's pack-smoke job makes. ⛔ Do not add flags or env here
# to make a red go away: the assertions are #14000's deliverable and a
# refusal this script reports is a refusal a user would get.
- name: Publish smoke (packed tarballs)
run: bash scripts/publish-smoke.sh
# Keeps only the entries the build's run summary (`.turbo/runs/`) names,
# as ci.yml Test Core's prune step does (#25452): the restore step takes
# the newest seed by prefix and turbo never evicts its local cache, so a
# seed saved whole is every seed before it plus this run. The save keeps
# the gate the combined `actions/cache` post-step had: a green job whose
# exact key missed, and a save failure never fails the job.
- name: Prune the Turbo cache to this run's tasks
id: prune-turbo-cache
if: success() && steps.turbo-cache.outputs.cache-hit != 'true'
run: |
node scripts/ci/prune-turbo-cache.mjs --site 'Packed-tarball smoke (opt-in)' \
--cache .turbo/cache --run-summaries .turbo/runs
- name: Save turbo cache
if: >-
success()
&& steps.turbo-cache.outputs.cache-hit != 'true'
&& steps.prune-turbo-cache.outputs.save == 'true'
uses: actions/cache/save@v6
timeout-minutes: 5
continue-on-error: true
with:
path: .turbo/cache
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}