-
Notifications
You must be signed in to change notification settings - Fork 321
402 lines (352 loc) · 15.5 KB
/
Copy pathci.yml
File metadata and controls
402 lines (352 loc) · 15.5 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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
name: CI
on:
# Run full CI when code lands on main (or other long-lived branches)
push:
branches:
- main
# Run CI for all PR updates without duplication from push events
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
branches: ['**']
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Detect file changes to optimize CI performance (THU-28)
# Skip Rust build step when no Rust-related files have changed
detect-changes:
runs-on: ubuntu-latest
outputs:
rust: ${{ steps.filter.outputs.rust }}
agent-core: ${{ steps.filter.outputs.agent-core }}
cli: ${{ steps.filter.outputs.cli }}
wasm-artifact: ${{ steps.filter.outputs.wasm-artifact }}
crate-src: ${{ steps.filter.outputs.crate-src }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Detect changed files
id: filter
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
with:
filters: |
rust:
- 'src-tauri/**/*.rs'
- 'src-tauri/**/Cargo.toml'
- 'src-tauri/**/Cargo.lock'
- 'src-tauri/build.rs'
- 'src-tauri/rust-toolchain.toml'
- 'src-tauri/.cargo/**'
- 'crates/**'
agent-core:
- 'shared/agent-core/**'
cli:
- 'cli/**'
wasm-artifact:
- 'src/acp/iroh/pkg/**'
# Inputs that change the compiled wasm (README.md excluded — it doesn't).
# Pairs with the wasm-artifact staleness gate: touching any of these MUST
# ship a regenerated src/acp/iroh/pkg in the same PR.
crate-src:
- 'crates/thunderbolt-acp-client/src/**'
- 'crates/thunderbolt-acp-client/Cargo.toml'
- 'crates/thunderbolt-acp-client/Cargo.lock'
- 'crates/thunderbolt-acp-client/rust-toolchain.toml'
- 'crates/thunderbolt-acp-client/.cargo/**'
- 'crates/thunderbolt-acp-client/build.sh'
typescript:
runs-on: ubuntu-latest
# Happy path is a few minutes; the GitHub default is 360min (6h). A 20min cap
# kills a CPU-starved runner ~18x sooner and frees the cancel-in-progress slot
# instead of blocking the concurrency group for hours.
timeout-minutes: 20
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
- name: Install Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Cache Bun dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.bun/install/cache
node_modules
key: bun-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Type check
run: bun run tsc --noEmit
- name: Lint
run: bun run lint
- name: Run tests
run: bun run test:5x
# Upload source maps to PostHog only for main branch
- name: Build application with source maps
if: github.ref == 'refs/heads/main'
run: bun run build
env:
NODE_ENV: production
NODE_OPTIONS: --max-old-space-size=4096
ENABLE_SOURCEMAP: 'true'
- name: Inject & upload source maps to PostHog
if: github.ref == 'refs/heads/main'
uses: PostHog/upload-source-maps@e798a054427efc710af080354f8450d3c154c584 # v0.4.6
with:
directory: dist
env-id: ${{ secrets.POSTHOG_CLI_ENV_ID }}
cli-token: ${{ secrets.POSTHOG_CLI_TOKEN }}
- name: Delete source maps after upload
if: github.ref == 'refs/heads/main'
run: find dist -name '*.map' -delete
- name: Compute and save metrics baseline
if: github.ref == 'refs/heads/main'
run: |
bun test --cwd=src --timeout 3600000 --coverage 2>&1 | tee /tmp/coverage.txt || true
COVERAGE=$(grep -E "All files" /tmp/coverage.txt | grep -oE '[0-9]+\.[0-9]+' | head -1)
bunx size-limit --json > /tmp/size-limit.json 2>/dev/null || true
BUNDLE=$(jq -r '.[0].size // 0' /tmp/size-limit.json 2>/dev/null || echo 0)
mkdir -p .metrics-baseline
printf '{"bundleSize":%s,"coverage":"%s"}\n' "${BUNDLE:-0}" "${COVERAGE:-N/A}" > .metrics-baseline/metrics.json
- name: Cache metrics baseline
if: github.ref == 'refs/heads/main'
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: .metrics-baseline
key: pr-metrics-main-${{ github.sha }}
# shared/agent-core is an isolated module (also consumed by cli/). Its unit tests
# are NOT part of the default `bun run test` — they only need to run when the
# module itself changes, so gate them on the agent-core path filter. Mirrors the
# `rust` job's detect-changes pattern and the `typescript` job's Bun setup.
agent-core:
needs: detect-changes
if: needs.detect-changes.outputs.agent-core == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
- name: Install Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Cache Bun dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.bun/install/cache
node_modules
key: bun-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Run agent-core tests (5x)
run: bun run test:agent-core:5x
# cli/ is a self-contained package (own bun.lock, no imports from src/ or shared/),
# so its tests and typecheck aren't reached by the `typescript` job. Gate on the
# `cli/**` path filter — same detect-changes mechanism as the `rust` and
# `agent-core` jobs — so it runs only when cli/ changes.
cli:
needs: detect-changes
if: needs.detect-changes.outputs.cli == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14
- name: Cache Bun dependencies (cli)
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.bun/install/cache
cli/node_modules
key: cli-bun-${{ hashFiles('cli/bun.lock') }}
restore-keys: |
cli-bun-
- name: Install cli dependencies
run: cd cli && bun install --frozen-lockfile
- name: Type check cli
run: cd cli && bun run typecheck
- name: Run cli tests (5x)
run: cd cli && bun run test:5x
# The iroh ACP client (P2P/QUIC crypto core) ships as a prebuilt wasm artifact at
# src/acp/iroh/pkg so the web app imports it without a wasm toolchain in CI. The
# build remaps absolute cargo/repo paths for local reproducibility; cross-platform
# Linux/macOS reproducibility remains unproven. CI therefore enforces provenance
# two ways without rebuilding or needing a wasm toolchain:
# 1. Staleness gate — a change to the crate source MUST ship a regenerated pkg/ in
# the same PR, so editing the crate can't silently leave the committed wasm stale
# (the `rust` job host-compiles the crate but never touches the binary).
# 2. Tamper-evidence — the committed pkg/ must match its CHECKSUMS.txt manifest.
# Reproduce a bit-identical artifact locally on the pinned toolchain with
# `crates/thunderbolt-acp-client/build.sh --verify` (see that crate's README).
wasm-artifact:
needs: detect-changes
if: needs.detect-changes.outputs.wasm-artifact == 'true' || needs.detect-changes.outputs.crate-src == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
# Crate source changed but the committed pkg/ didn't — the wasm wasn't rebuilt,
# so it's now stale relative to its source. Fail with the fix instruction.
- name: Guard against stale wasm artifact
if: needs.detect-changes.outputs.crate-src == 'true' && needs.detect-changes.outputs.wasm-artifact != 'true'
run: |
echo "::error::crates/thunderbolt-acp-client changed but src/acp/iroh/pkg was not regenerated — the committed wasm is stale."
echo "Rebuild and commit the artifact in the same change: crates/thunderbolt-acp-client/build.sh (then commit src/acp/iroh/pkg)."
exit 1
- name: Verify committed wasm artifacts match checksums manifest
if: needs.detect-changes.outputs.wasm-artifact == 'true'
run: |
cd src/acp/iroh/pkg
diff -u \
<(awk '{ print $2 }' CHECKSUMS.txt | sort) \
<(find . -maxdepth 1 -type f ! -name CHECKSUMS.txt -exec basename {} \; | sort)
shasum -a 256 -c CHECKSUMS.txt
rust:
needs: detect-changes
if: needs.detect-changes.outputs.rust == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
with:
components: rustfmt, clippy
cache: false
- name: Cache cargo registry
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('src-tauri/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-registry-
# Security tripwire: fail if the unsound glib::VariantStrIter path
# (GHSA-wrw7-89jp-8q8g) ever gains a caller in our code or any dependency.
# Cheap (cargo metadata + grep, no build), so it runs before the build.
- name: Guard glib VariantStrIter unsoundness (RUSTSEC-2024-0429)
run: ./scripts/check-glib-variantstriter.sh
- name: Cache cargo build
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: src-tauri/target/
key: ${{ runner.os }}-cargo-build-${{ hashFiles('src-tauri/Cargo.lock') }}-${{ hashFiles('src-tauri/src/**/*.rs') }}
restore-keys: |
${{ runner.os }}-cargo-build-${{ hashFiles('src-tauri/Cargo.lock') }}-
${{ runner.os }}-cargo-build-
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libxdo-dev \
libssl-dev \
libayatana-appindicator3-dev \
librsvg2-dev
- name: Build and check Rust code
env:
CARGO_INCREMENTAL: 0
RUSTC_WRAPPER: ''
run: |
cd src-tauri
cargo build --all-targets
cargo clippy --all-targets -- -D warnings
cargo test
# crates/ holds the standalone thunderbolt-acp-client crate (own Cargo.toml
# and Cargo.lock, not a src-tauri workspace member), so the src-tauri build
# above never compiles it. Its wasm cdylib is the deployed artifact; the rlib
# target lets these host-target checks run without a wasm toolchain — the
# .cargo/config.toml rustflags are scoped to wasm32, so no --target override.
- name: Build and check ACP client crate
env:
CARGO_INCREMENTAL: 0
RUSTC_WRAPPER: ''
run: |
cd crates/thunderbolt-acp-client
cargo build --all-targets
cargo clippy --all-targets -- -D warnings
cargo test
backend:
runs-on: ubuntu-latest
# Healthy run is ~130s; a CPU-starved one was observed at 4920s (~82min). A
# 15min cap = >6x headroom over the worst legitimate run (even with the 5x
# rerun gate) while killing a starved run ~24x sooner than the 360min default.
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Bun
# Pinned to 1.3.13 to match main (green). The 1.3.14 bump (a5bbd883) was for
# the frontend --isolate work that Option A dropped; on the backend job 1.3.14
# correlated with intermittent hard hangs inside PGlite's WASM (the 5s test
# timeout couldn't even fire — 94s/10min stalls), never seen on main's 1.3.13.
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.13
- name: Cache Bun dependencies (backend)
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.bun/install/cache
backend/node_modules
key: backend-bun-${{ hashFiles('backend/bun.lock') }}
restore-keys: |
backend-bun-
- name: Install backend dependencies
run: |
cd backend
bun install --frozen-lockfile
- name: Type check backend
run: |
cd backend
bun run type-check
- name: Lint backend
run: |
cd backend
bun run lint
- name: Test backend (5x, excludes same-process WS event-delivery flakes)
timeout-minutes: 10
# No per-test timeout in CI (large value ≈ disabled — bun `--timeout 0` hangs async tests):
# the 10-minute step cap above is the real ceiling, so tests that only get slow under
# CI/PGlite contention don't spuriously fail. Local `test:backend` keeps the 5s timeout.
run: cd backend && bun test --timeout 3600000 --randomize --rerun-each 5 $(find src -name '*.test.ts' -not -path '*proxy/ws-e2e.test.ts' -not -path '*haystack/routes.test.ts')
# ws-e2e.test.ts + haystack/routes.test.ts wait on Bun same-process WS close/
# message events that Bun drops/delays under load. Systematic causes (shared
# PGlite, missing event waits, strict close codes) are already fixed; the
# residual is irreducible same-process WS event-drop. Run them ONCE (no
# --rerun-each, which 5x'd the exposure) and retry up to 5x so an intermittent
# drop self-heals while a genuine break (5 non-zero) still reds CI.
- name: Test backend (segregated WS, 1x + retry)
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
with:
timeout_minutes: 5
max_attempts: 5
retry_wait_seconds: 5
retry_on: error
command: bun run test:backend:ws