|
| 1 | +# Opt-in pre-merge pack smoke (#14214, derived from the #14000 ruling). |
| 2 | +# |
| 3 | +# ## The gap this closes |
| 4 | +# |
| 5 | +# `publish-smoke.yml` owns the pack smoke, and it runs on `workflow_run` AFTER a |
| 6 | +# Release run — it resolves the changesets release branch and reports its verdict |
| 7 | +# as a commit status on that branch head. That is the right place for a release |
| 8 | +# verdict and the wrong place for a PR author: #11767 (audience default flipped |
| 9 | +# to `invite_only`, a breaking auth change) merged with ZERO packed-install |
| 10 | +# coverage on its own PR, and the smoke then sat red on the release candidate for |
| 11 | +# ~7 days because the only surface carrying the verdict was one no PR author |
| 12 | +# looks at (measured in #14000). |
| 13 | +# |
| 14 | +# This workflow adds the missing TRIGGER — and only the trigger. The driver, the |
| 15 | +# probes and every assertion stay exactly as `scripts/publish-smoke.sh` defines |
| 16 | +# them; re-pinning those to the declared first-run contract was #14000's own |
| 17 | +# deliverable (PR #14255) and is deliberately untouched here. What is new is |
| 18 | +# WHEN the smoke runs and WHERE its verdict lands: on the pull request, before |
| 19 | +# the merge, as an ordinary check run the author already reads. |
| 20 | +# |
| 21 | +# ## Opt-in, by ruling — and what that buys and costs |
| 22 | +# |
| 23 | +# Maintainer ruling 2026-09-01 (director batch #23, 「同意」) approved the |
| 24 | +# LABEL-GATED OPT-IN shape: the cost stays in opt-in. A full build + pack + |
| 25 | +# clean install is ~45 minutes, so putting it in the default inner loop would |
| 26 | +# tax every PR in the repo to cover the handful that can break a fresh install. |
| 27 | +# |
| 28 | +# ⛔ Therefore this workflow must never grow a `paths:` filter that runs it by |
| 29 | +# default, and must never be added to `.github/labeler.yml`. Auto-applying the |
| 30 | +# label from changed paths would be a DIFFERENT design (auto-detect), not the |
| 31 | +# one that was ruled, and it would reintroduce the default-inner-loop cost the |
| 32 | +# ruling removed. |
| 33 | +# |
| 34 | +# The residual risk is stated rather than hidden: self-declaration only covers |
| 35 | +# the author who RECOGNISES their change as breaking. An author who does not |
| 36 | +# realise they widened the unauthenticated surface still gets no pre-merge pack |
| 37 | +# coverage — the release-time leg in `publish-smoke.yml` remains the backstop for |
| 38 | +# that case. Closing that gap would require a detection rule, which is a |
| 39 | +# different card and a different ruling. |
| 40 | +# |
| 41 | +# ## When to apply the label |
| 42 | +# |
| 43 | +# Apply `needs:pack-smoke` to your own PR when it changes the auth/audience |
| 44 | +# DEFAULTS or the accept/reject behaviour of the unauthenticated surface. The |
| 45 | +# same criterion is written where PR authors meet it, in CONTRIBUTING.md. |
| 46 | +# |
| 47 | +# ## Wiring decisions |
| 48 | +# |
| 49 | +# `types:` restates GitHub's three defaults alongside `labeled`, because naming |
| 50 | +# `types:` REPLACES the default set rather than extending it (#8304) — dropping |
| 51 | +# one produces no run on that activity, which is an absence rather than a skip. |
| 52 | +# `opened` is in the list so a PR created via the API already carrying the label |
| 53 | +# is smoked; `synchronize` is the load-bearing one, because the thing under test |
| 54 | +# is the MERGE PREVIEW and every push changes it. |
| 55 | +# |
| 56 | +# The guard has two limbs and both are needed: |
| 57 | +# |
| 58 | +# 1. the PR must CURRENTLY carry the label — this is what makes an unlabelled |
| 59 | +# PR cost nothing; |
| 60 | +# 2. on a `labeled` event the label just added must be OURS. Without this limb |
| 61 | +# every unrelated label the size labeler adds (`size/l`, `ci/cd`, …) to an |
| 62 | +# already-labelled PR would start another 45-minute smoke of a tree that |
| 63 | +# has not changed. |
| 64 | +# |
| 65 | +# `concurrency` sits at JOB level, not workflow level, and that is deliberate. |
| 66 | +# At workflow level the group is taken by every run this workflow starts, |
| 67 | +# including the runs whose job then skips — so an unrelated `labeled` event |
| 68 | +# would CANCEL a smoke that was still running and then skip, leaving the PR with |
| 69 | +# no verdict at all. That is the #14000 silence shape (a missing answer reading |
| 70 | +# as a green one) rebuilt inside its own fix. A skipped job never enters a |
| 71 | +# job-level group, so only a real smoke can supersede a real smoke. |
| 72 | +# |
| 73 | +# ⛔ No `merge_group:` trigger, and this is not the oversight it resembles. A |
| 74 | +# `merge_group` event carries no `pull_request` context, so neither limb of the |
| 75 | +# guard above can be evaluated there; and the ruling puts this cost pre-merge and |
| 76 | +# opt-in, not in the queue. This job is correspondingly NOT a required context |
| 77 | +# (`scripts/check-required-contexts.mjs` holds that registry) — it is advisory by |
| 78 | +# design, and a PR that never opts in simply never produces it. |
| 79 | + |
| 80 | +name: Pack Smoke (opt-in) |
| 81 | + |
| 82 | +on: |
| 83 | + pull_request: |
| 84 | + branches: |
| 85 | + - main |
| 86 | + types: [opened, synchronize, reopened, labeled] |
| 87 | + |
| 88 | +permissions: |
| 89 | + contents: read |
| 90 | + |
| 91 | +jobs: |
| 92 | + pack-smoke: |
| 93 | + # ~45 minutes of build + pack + clean install. Opt-in only — see the header. |
| 94 | + name: Packed-tarball smoke (opt-in) |
| 95 | + if: >- |
| 96 | + contains(github.event.pull_request.labels.*.name, 'needs:pack-smoke') |
| 97 | + && (github.event.action != 'labeled' || github.event.label.name == 'needs:pack-smoke') |
| 98 | + runs-on: ubuntu-latest |
| 99 | + timeout-minutes: 45 |
| 100 | + concurrency: |
| 101 | + group: pack-smoke-optin-${{ github.event.pull_request.number }} |
| 102 | + cancel-in-progress: true |
| 103 | + steps: |
| 104 | + # No `ref:` — on `pull_request` the default checkout is `refs/pull/N/merge`, |
| 105 | + # the MERGE PREVIEW, which is the tree the ruling names: what `main` will |
| 106 | + # actually contain, not what the branch contains in isolation. The driver |
| 107 | + # packs from the checkout root (`REPO_ROOT` in scripts/publish-smoke.sh), |
| 108 | + # so the preview is what gets packed, installed and probed. |
| 109 | + - name: Checkout the merge preview |
| 110 | + uses: actions/checkout@v7 |
| 111 | + |
| 112 | + - name: Setup Node.js |
| 113 | + uses: actions/setup-node@v7 |
| 114 | + with: |
| 115 | + node-version: '22' |
| 116 | + |
| 117 | + - name: Setup pnpm |
| 118 | + uses: ./.github/actions/setup-pnpm |
| 119 | + |
| 120 | + - name: Get pnpm store directory |
| 121 | + shell: bash |
| 122 | + run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV |
| 123 | + |
| 124 | + - name: Setup pnpm cache |
| 125 | + uses: actions/cache@v6 |
| 126 | + with: |
| 127 | + path: ${{ env.STORE_PATH }} |
| 128 | + key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} |
| 129 | + restore-keys: | |
| 130 | + ${{ runner.os }}-pnpm-store-v3- |
| 131 | +
|
| 132 | + - name: Setup turbo cache |
| 133 | + uses: actions/cache@v6 |
| 134 | + with: |
| 135 | + path: .turbo/cache |
| 136 | + key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }} |
| 137 | + restore-keys: | |
| 138 | + ${{ runner.os }}-turbo-${{ github.job }}- |
| 139 | + ${{ runner.os }}-turbo- |
| 140 | +
|
| 141 | + - name: Install dependencies |
| 142 | + run: pnpm install --frozen-lockfile |
| 143 | + |
| 144 | + # The driver's own prerequisite, asserted by the script itself: pack mode |
| 145 | + # fails fast unless packages/cli/dist and the create-objectstack bin exist. |
| 146 | + - name: Build |
| 147 | + run: pnpm run build |
| 148 | + |
| 149 | + # SMOKE_MODE defaults to `pack`, so this is the same invocation |
| 150 | + # publish-smoke.yml's pack-smoke job makes. ⛔ Do not add flags or env here |
| 151 | + # to make a red go away: the assertions are #14000's deliverable and a |
| 152 | + # refusal this script reports is a refusal a user would get. |
| 153 | + - name: Publish smoke (packed tarballs) |
| 154 | + run: bash scripts/publish-smoke.sh |
0 commit comments