Repository navigation
192 lines (192 loc) · 10.7 KB
/
Copy pathci.yml
File metadata and controls
192 lines (192 loc) · 10.7 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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# Release gate mirror (2.336.0): the same battery the pre-push hook enforces
# locally, run on every push/PR so a bypassed or missing local hook still gets
# caught in minutes. Self-hosters see the badge state before pulling an update.
#
# TWO JOBS since the fast/heavy split (2026-09-07): `fast` is the one whose red
# X must arrive quickly — THE TIER RULE in scripts/ci.mjs (B-f4cb, 2026-09-27):
# build + PURE / in-process suites under 10 s + the named LAW pins + the one
# real haiku turn — and `heavy` is everything that boots a scratch server,
# drives a browser, runs a real agent CLI / X server / dtach / the opencode
# binary, or takes 10 s or more. They run in
# PARALLEL — heavy is not a consequence of fast, it is the other half of the
# same battery, and making it `needs: fast` would only delay the slow signal.
# The census (`--census`, also asserted inside `npm run build`) is what
# guarantees the two jobs together cover every scripts/test-*.mjs.
name: ci
on:
push:
# ci/** (lane ci-aidev-desktop-batch, q-038): a train or a lane pushed to a
# ci/<name> branch gets the same mirror before it reaches master
branches: [master, 'ci/**']
pull_request:
jobs:
fast:
runs-on: ubuntu-latest
# 15 (B-f4cb). It was CANCELLED at 15 on .202/.203/.205: the tier had
# grown back to 269 suites run one after another (792 s summed locally,
# 1.3× that here). Lane fast-budget (2026-10-04) fixed or moved every
# suite over 10 s and runs the tier over min(4, cpus) lanes + a serial
# lane for the clock judges (scripts/ci.mjs FAST LANES). Measured in this
# runner's shape (depth-1 clone, node 22, taskset 0-3): 165 s for build +
# suites (was ≈ 845 s), ≈ 215 s at this runner's 1.3× plus ~35 s of
# setup — about 4 min, under a third of this budget.
timeout-minutes: 15
# EVERY SETUP STEP IS BOUNDED (lane ci-cjk-fonts, 2.369.346): a stall is a red that names its step in minutes, never
# a silence until the job's limit (.345's heavy 4/6 sat 21 min in one apt fetch). Each bound is a few times the
# step's measured worst (2026-10-23, 13 jobs: checkout 6–10 s, setup-node 0–3 s, dtach 7–67 s, claude CLI 3–6 s,
# npm ci 8–14 s); the suite step's bound is its job's. test-ci-gate §2h holds every job to it.
steps:
- uses: actions/checkout@v4
timeout-minutes: 3
- uses: actions/setup-node@v4
timeout-minutes: 3
with:
node-version: 22
- name: Install dtach (session persistence layer the boot smoke needs)
run: bash scripts/ci-apt.sh dtach
timeout-minutes: 3
- name: Install claude CLI (chat E2E; subscription oat auth is the
officially documented CI channel — docs/en/github-actions)
run: bash scripts/ci-npm.sh install -g @anthropic-ai/claude-code
timeout-minutes: 5
# THE ONE NPM DOOR (lane ci-npm-retry, B-fa5e): .330's heavy 6/6 died in
# `npm ci` on a registry reset MID-DOWNLOAD — a blip npm's own
# fetch-retries never covers (it retries a request until its headers).
# An INSTALL may be retried: 3 fresh attempts 20 s apart, each failed one
# a ::warning::, red only after the third. A SUITE never is — a flake is
# a race (test-ci-gate §2g holds both).
- name: npm ci (3 attempts on a registry blip)
run: bash scripts/ci-npm.sh ci
timeout-minutes: 6
- run: node scripts/ci.mjs
env:
# claude setup-token secret; absent on fork PRs → chat E2E SKIPs
VIBESPACE_CI_OAT: ${{ secrets.VIBESPACE_CI_OAT }}
# HOSTED HALF ONLY (lane ci-aidev-desktop-batch, design q-038 step ①,
# 2026-10-14): `--host=hosted` runs the heavy suites a hosted runner CAN run
# (scripts/ci.mjs HOSTS — derived from the programs each suite's code names);
# the self-hosted half (xpra / Xvfb / x11vnc / LibreOffice / codex / opencode /
# bwrap / FUSE — 48 suites that only SKIPPED here) is the desktop batch on
# AIDev (`node scripts/ci-remote.mjs --host aidev --batch desktop --lanes 8`).
# SIX shards, packed LONGEST-FIRST (LPT) by this runner's own per-suite times
# checked in at scripts/fixtures/ci-shard-timings.json (Actions runs
# 38504978786 + 38459476258): 1683 s of suites each ≈ 14 min over two lanes,
# vs 6.1–18.6 min by the name hash. test-ci-gate §8b/§8c hold the matrix to
# every slice and the two halves to a partition of the tier.
# SHARDED (lane mirror-green r2, 2026-10-03): ONE heavy job hit its 60-min
# timeout on .199/.200 — 216 suites over the runner's two lanes (4 cpus) had
# drained in 2611–2950 s already on .196–.198. `--shard i/4` runs one slice
# of the tier (scripts/ci.mjs SHARDS: the split is a function of the table's
# names and n alone, so the four jobs agree; the serial group is shard 1's;
# test-ci-gate §8b holds every heavy suite in exactly one slice and this
# matrix to every slice). The runner's own per-suite times (.198 + .200
# logs) put the slices at ~11–21 min of suites over two lanes, ~25 min with
# setup at worst — 45 is each job's own bound. fail-fast off: a red slice
# must not cancel the others (the tier reports ALL the damage). `heavy`
# below is the ONE status that reads green/red for the whole tier.
heavy-shards:
name: heavy ${{ matrix.shard }}/6
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5, 6]
timeout-minutes: 45
# every setup step bounded — the fast job's rule and numbers
steps:
- uses: actions/checkout@v4
timeout-minutes: 3
- uses: actions/setup-node@v4
timeout-minutes: 3
with:
node-version: 22
- name: Install dtach (session persistence layer the boot smokes need)
run: bash scripts/ci-apt.sh dtach
timeout-minutes: 3
# CJK fonts (lane-mirror-197, 2.369.198): the runner image ships DejaVu +
# Liberation and NO CJK family, so every zh / ja glyph the chrome suites
# measure was DejaVu's fallback box — narrower than the glyph a Chinese or
# Japanese user sees. The zh / ja "drawn whole" censuses were blind here
# (a box that fits under tofu can be cut under Noto Sans CJK, what users
# and this project's own machine render), and a control whose premise was
# "the pre-fix rule cuts this ja line at 360 px" went red for the font
# alone. Users have CJK fonts; the runner gets the same family. The
# controls stay font-independent regardless (test-channel-threads-ui ⑥).
# THE ONE FONTS ROAD (lane ci-cjk-fonts, 2.369.346): `apt install fonts-noto-cjk` took 3–6 s where the azure mirror
# served it and 11–21 min where it did not (2026-10-23: 4 of 6 shards on a branch run, 2 of 6 on the .345 master
# mirror — its heavy 4/6 cancelled at 45 with 22 min left for 25 min of suites).
# scripts/ci-fonts.sh fetches the SAME bytes — the four TTCs at the noto-cjk tag the runner's .deb repacks, each
# sha256-pinned and verified before install; this cache keeps them, keyed on that file, so a hit is a sha256 check
# (a stalled restore is a miss after 2 min, never a wait). --system makes them every HOME's, as apt's were (the
# suites' servers run under scratch HOMEs). No apt fallback: a second road would hide the first one's failure.
- name: Cache the pinned CJK fonts
uses: actions/cache@v4
timeout-minutes: 3
env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 2
with:
path: ~/.local/share/fonts/noto-cjk
key: noto-cjk-${{ hashFiles('scripts/ci-fonts.sh') }}
- name: Install CJK fonts (the runner measures zh / ja text as users see it)
run: bash scripts/ci-fonts.sh --system
timeout-minutes: 6
- name: Install claude CLI
run: bash scripts/ci-npm.sh install -g @anthropic-ai/claude-code
timeout-minutes: 5
# the ONE npm door (see the fast job): installs retried, suites never
- name: npm ci (3 attempts on a registry blip)
run: bash scripts/ci-npm.sh ci
timeout-minutes: 6
# Runner images ship Chrome; the chrome suites SKIP with a reason if a
# future image drops it, so this never becomes a silent pass.
#
# --dirty-ok (round 2): here the EXIT CODE is the signal — the runner is
# thrown away, so the `data/ci-heavy/<sha>` marker nobody will ever read
# is not what this job is for. Locally that refusal is the point (it
# stops a developer spending 16 minutes to be told the run could not
# produce the verdict they were told to produce); in Actions it would
# only turn "some step touched a tracked file" into a red gate.
#
# BECAUSE THE EXIT CODE IS THE SIGNAL, no "no verdict" path may return 0
# (round 4): 0 green · 1 red · 2 refused · 3 never got the machine lock ·
# 4 ABORTED. A suite that CRASHES (a V8 heap-limit OOM is SIGABRT, the
# kernel OOM killer is SIGKILL) is a RED here, not a supersession — that
# confusion is what once showed this job a green tick for a tier that
# crashed and then ran nothing.
- run: node scripts/ci.mjs --heavy --dirty-ok --host=hosted --shard ${{ matrix.shard }}/6
env:
VIBESPACE_CI_OAT: ${{ secrets.VIBESPACE_CI_OAT }}
VS_UI_SHOTS_DIR: ${{ runner.temp }}/vs-shots
# A chrome suite that goes red HERE and green locally (2.369.125's
# test-gear-menu Esc legs) leaves its screenshots on a runner nobody can
# log into; ship them as an artifact so the failure can be read instead
# of guessed at. Lane mirror-315-webrtc-pin: since B-60d2 a suite's
# scratch dir (the old /tmp/vs-*-shots-* glob) is removed at its exit —
# the .315 red uploaded nothing — so the PNGs go to VS_UI_SHOTS_DIR, and
# with them every failed try's WHOLE output (ci.mjs's fail-logs; the log
# above keeps a tail).
- name: Upload the failed suites' whole logs + chrome-suite screenshots (failure only)
if: failure()
uses: actions/upload-artifact@v4
timeout-minutes: 5
with:
name: chrome-suite-shots-${{ github.sha }}-${{ matrix.shard }}
path: |
data/ci-heavy/fail-logs/*.log
${{ runner.temp }}/vs-shots/
if-no-files-found: ignore
retention-days: 7
# THE ONE HEAVY STATUS: green only when every shard is green. always(), so a
# red or cancelled shard turns THIS job red instead of leaving it skipped —
# a skipped check reads as "nothing to say".
heavy:
needs: heavy-shards
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: every heavy shard green
timeout-minutes: 1
run: |
echo "heavy shards: ${{ needs.heavy-shards.result }}"
test "${{ needs.heavy-shards.result }}" = success