Skip to content

Commit ab98901

Browse files
ci: an opt-in label lane runs the 18.x upgrade harness on a line PR's merge preview (step 0 wiring) (#24554)
Part of #24522 Clause-②: no — a CI workflow, an expected-skip roster row and a label declaration; nothing a package publishes changes The second, governed half of step 0 of the 18.x line: the **wiring** that lets the line's PRs run the upgrade harness **before** they merge. The harness itself (test, fixture, generator) is the sibling draft PR on `claude/issue-24522-18x-upgrade-harness`, and this PR's job runs that file. Land it **with or after** that PR. Before then, a labelled run fails loudly on a missing file: the dogfood config's filter preflight refuses a named path that selects no test. ## 维护者速读(草稿) - **改了什么:** 新增一个按标签触发的 CI 任务。PR 打上 `needs:upgrade-harness` 标签后,CI 会在合并预览上用 18.0 的样本数据库启动当前代码,检查数据是否完好、v17 数据库是否仍被拒绝。没有标签的 PR 不运行,不花钱。另外两处是配套登记:预期跳过名单加一行,标签声明脚本加一行。 - **为什么改:** 您定的 18.x 原则是每次合并后 `main` 都能随时发版,其中 R2 要求「18.0 装出来的数据库升级到这个提交后能启动、数据不丢」,并要求在合并前检查。dogfood 测试现在只在 `main` 上跑,PR 阶段跑不到,所以要加这个入口。 - **风险与代价(含回滚):** 每个打了标签的 PR 约多一个 CI 任务:构建 dogfood 依赖(有缓存时较快,冷构建约 10 分钟)加约 1 分钟的测试。它不是必需检查,不进合并队列,也不影响其他 PR。回滚方法是删掉这个 workflow 文件。 - **席位意见:** - **你要做的:** 审阅并批准合并(`.github/workflows/**` 属 Tier H)。标签对象由席位用 `scripts/pm/ensure-pm-labels.sh` 创建;之后席位给 18.x 线上的每个 PR 打这个标签。 ## What it adds - `.github/workflows/upgrade-harness-optin.yml` — job `18.x upgrade harness (opt-in)`. - It runs on `pull_request` (opened, synchronize, reopened, labeled), and only while the PR carries `needs:upgrade-harness`. A `labeled` event must name that label. - It checks out the merge preview, restores the dogfood job's turbo cache read-only, builds `--filter=@objectstack/dogfood`, and runs `vitest run test/upgrade-18x-harness.dogfood.test.ts`. - Concurrency is set at job level, so a skipped run never cancels a real one. There is no `merge_group` trigger, and the job is not a required context. This mirrors `pack-smoke-optin.yml`, whose header argues each choice. - `scripts/pm/check-expected-skips.mjs` — the job's name enters `EXPECTED_SKIPS` as a label-gated skip, so the enqueue bar does not read its skip on every other PR as a filter miss. - `scripts/pm/ensure-pm-labels.sh` — declares the label object (colour and description). ⛔ Never auto-applied. ## Why this shape (measured) | wiring | cost per line PR | why not / why | |:--|:--|:--| | run it on every PR | build closure plus ~54 s on every PR in the repo | taxes every PR for one release line | | `workflow_dispatch` `ci.yml` on the PR branch | the full battery: 3 dogfood shards, Temporal Conformance and the console pin | no diff needed, but a manual act with no trigger a PR carries, and ~20× the runner time for one file | | **label lane (this PR)** | the dogfood build closure (turbo cache restored) plus one file: 53.89 s locally, of which 42.8 s is import | runs only where the seat asks; the ruled shape for a pre-merge check most PRs do not need | ## Gates (on this branch, head as pushed) - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 52 commands. All 52 exit 0. - `pnpm check:pm-dispatch-gates` ran on its own: `✓ dispatch-gates self-test (fast tier): 1913 cases pass` in 465.4 s. - `pnpm check:pm-expected-skips` and `pnpm check:pm-label-desc-cap` exit 0. - `node scripts/pm/check-expected-skips.mjs --self-test` gave `✓ … 154 cases pass`; it reads the roster's truth against this checkout's workflows. - `node scripts/pm/check-governed-merges.mjs --branch claude/issue-24522-harness-lane` reads `GOVERNED`, landing tier **H**, 136 changed lines. --- _Generated by [Claude Code](https://claude.ai/code/session_01SH2ojNSqz93pYiNSmUyQnf)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3465ad4 commit ab98901

3 files changed

Lines changed: 136 additions & 0 deletions

File tree

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Opt-in pre-merge 18.x upgrade harness (#24522, step 0 of the 18.x line).
2+
#
3+
# ## The gap this closes
4+
#
5+
# Ruling record 6266750393 on #15194 makes `main` the release line: every PR on
6+
# the 18.x line, once merged, leaves `main` releasable, and its R2 reads "a
7+
# database an 18.0 install created upgrades to that commit, boots, and keeps
8+
# its data" — a check each of the line's PRs passes BEFORE it merges. The
9+
# harness that answers R2 is a dogfood test,
10+
# `packages/qa/dogfood/test/upgrade-18x-harness.dogfood.test.ts`, and the
11+
# dogfood jobs in `ci.yml` run only on `main`'s full battery (THE TWO SPEEDS,
12+
# #22998): they skip `pull_request` and `merge_group`. So without this workflow
13+
# the line's PRs get their first R2 answer after the merge.
14+
#
15+
# This workflow adds the missing TRIGGER for that ONE file, and nothing else.
16+
# The test, its fixture and its assertions stay where they are, and the full
17+
# battery keeps running the same file with the rest of dogfood on `main`.
18+
#
19+
# ## Opt-in, label-gated — the cheapest wiring measured
20+
#
21+
# Measured on the step-0 branch (the card's report carries the readings):
22+
#
23+
# - the harness file alone: ~54 s of vitest wall (one 4-vCPU box, ~43 s of it
24+
# module import), against the dogfood suite's ~5 min per shard × 3;
25+
# - its build closure is the dogfood package's (`turbo run build
26+
# --filter=@objectstack/dogfood`, 66 tasks), which the turbo cache below
27+
# replays; cold, it is the step's whole cost.
28+
#
29+
# Three shapes were weighed. Running it on every PR taxes the whole repository
30+
# for one release line. Dispatching `ci.yml` by hand on a line PR's branch needs
31+
# no diff but runs the entire full battery (three dogfood shards, Temporal
32+
# Conformance, the console pin) for one file, and is a manual act with no
33+
# trigger a PR carries. The label lane runs one file, only where asked: the
34+
# shape `pack-smoke-optin.yml` already ruled for a pre-merge check that most
35+
# PRs do not need. The epic seat hangs `needs:upgrade-harness` on each 18.x
36+
# line PR; ⛔ never auto-applied, never in `.github/labeler.yml`.
37+
#
38+
# ## Wiring decisions (the `pack-smoke-optin.yml` ones, for the same reasons)
39+
#
40+
# `types:` restates GitHub's defaults beside `labeled` (naming `types:`
41+
# REPLACES the default set). The guard has two limbs: the PR carries the label
42+
# now, and on a `labeled` event the label just added is OURS — otherwise every
43+
# size label would start another run of an unchanged tree. `concurrency` sits at
44+
# JOB level, so a skipped run never cancels a real one. ⛔ No `merge_group:`
45+
# trigger: a `merge_group` event carries no pull request, so the label cannot be
46+
# read there, and this context is NOT a required one
47+
# (`scripts/check-required-contexts.mjs`) — the seat reads it before it queues a
48+
# line PR, the way the ruling's R2 asks.
49+
50+
name: Upgrade Harness (opt-in)
51+
52+
on:
53+
pull_request:
54+
branches:
55+
- main
56+
types: [opened, synchronize, reopened, labeled]
57+
58+
permissions:
59+
contents: read
60+
61+
jobs:
62+
upgrade-harness:
63+
name: 18.x upgrade harness (opt-in)
64+
if: >-
65+
contains(github.event.pull_request.labels.*.name, 'needs:upgrade-harness')
66+
&& (github.event.action != 'labeled' || github.event.label.name == 'needs:upgrade-harness')
67+
runs-on: ubuntu-latest
68+
timeout-minutes: 30
69+
concurrency:
70+
group: upgrade-harness-optin-${{ github.event.pull_request.number }}
71+
cancel-in-progress: true
72+
steps:
73+
# No `ref:` — the MERGE PREVIEW: what `main` will contain once this PR
74+
# lands, which is the commit R2 is a statement about.
75+
- name: Checkout the merge preview
76+
uses: actions/checkout@v7
77+
78+
- name: Setup Node.js
79+
uses: actions/setup-node@v7
80+
with:
81+
node-version: '22'
82+
83+
- name: Setup pnpm
84+
uses: ./.github/actions/setup-pnpm
85+
86+
- name: Get pnpm store directory
87+
shell: bash
88+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
89+
90+
- name: Setup pnpm cache
91+
uses: actions/cache@v6
92+
with:
93+
path: ${{ env.STORE_PATH }}
94+
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
95+
restore-keys: |
96+
${{ runner.os }}-pnpm-store-v3-
97+
98+
# Restore-only: the dogfood job's entries (seeded from `main`) replay this
99+
# closure; a PR never writes the cache, as in ci.yml.
100+
- name: Restore Turbo cache
101+
uses: actions/cache/restore@v6
102+
with:
103+
path: .turbo/cache
104+
key: ${{ runner.os }}-turbo-dogfood-1-main-${{ github.sha }}
105+
restore-keys: |
106+
${{ runner.os }}-turbo-dogfood-1-main-
107+
${{ runner.os }}-turbo-dogfood-
108+
109+
- name: Install dependencies
110+
run: pnpm install --frozen-lockfile
111+
112+
- name: Build the dogfood package's dependency closure
113+
run: pnpm turbo run build --filter=@objectstack/dogfood --concurrency=4
114+
115+
# One file, named: the dogfood config's filter preflight refuses a path
116+
# that selects no test, so a renamed harness reds here instead of
117+
# passing over nothing.
118+
- name: Boot HEAD on the 18.0 fixture (R2) and check the v17 refusal (R3)
119+
run: pnpm --filter @objectstack/dogfood exec vitest run test/upgrade-18x-harness.dogfood.test.ts

‎scripts/pm/check-expected-skips.mjs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,13 @@ export const EXPECTED_SKIPS = Object.freeze([
269269
gate: { kind: 'label', label: 'needs:pack-smoke' },
270270
reason: 'opt-in by the `needs:pack-smoke` label; skipped on every PR that does not carry it, once per pull_request event',
271271
},
272+
{
273+
name: '18.x upgrade harness (opt-in)',
274+
workflow: 'upgrade-harness-optin.yml',
275+
job: 'upgrade-harness',
276+
gate: { kind: 'label', label: 'needs:upgrade-harness' },
277+
reason: 'opt-in by the `needs:upgrade-harness` label (the 18.x line\'s R2 check, #24522); skipped on every PR that does not carry it, once per pull_request event',
278+
},
272279
{
273280
name: 'Build Core',
274281
workflow: 'ci.yml',

‎scripts/pm/ensure-pm-labels.sh‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,16 @@ done
326326
# would assert a kinship the paragraph above spends its length denying.
327327
gh label create needs:pack-smoke -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge pack smoke: self-declared breaking auth/audience change (see CONTRIBUTING.md)" 2>/dev/null || true
328328

329+
# needs:upgrade-harness — the opt-in pre-merge 18.x upgrade harness (#24522;
330+
# ruling record 6266750393 on #15194, R2). Its consumer is the job guard in
331+
# `.github/workflows/upgrade-harness-optin.yml`, which boots the PR's merge
332+
# preview on the 18.0 fixture only while this label is on the PR. Like
333+
# needs:pack-smoke it names a property of the CHANGE, not a board position: the
334+
# epic seat hangs it on each 18.x line PR, nothing auto-applies it, and no sweep
335+
# in scripts/pm/ reads it. Same colour as needs:pack-smoke, the one family it
336+
# belongs to: an opt-in pre-merge check.
337+
gh label create needs:upgrade-harness -R objectstack-ai/objectstack -c 006b75 -d "Opt-in pre-merge 18.x upgrade harness: an 18.0 database boots on this PR's merge preview (#24522)" 2>/dev/null || true
338+
329339
# needs:contract-review — a MARKER, never a gate; rules in
330340
# .claude/skills/pm-dispatch/references/contract-review.md 〈等复核标记〉. The
331341
# dispatching seat hangs it on the PR at ACCEPT when either clause-② limb hits

0 commit comments

Comments
 (0)