Repository navigation
525 lines (497 loc) · 23.2 KB
/
Copy pathci.yml
File metadata and controls
525 lines (497 loc) · 23.2 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
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
name: CI
permissions:
contents: read
actions: read
on:
push:
# `train/**` is a PRECONDITION for CI-gated trains, not a convenience. Under required
# status checks a push to master is refused unless its sha already carries a green check,
# and a check only exists if a workflow ran on the branch that produced the sha. Enabling
# protection while this trigger is master-only makes master unpushable with no check ever
# arriving -- the fleet-wide shape QTA found, since nearly every seat triggers on its
# default branch alone. Widen the trigger BEFORE turning protection on.
branches: [master, 'train/**']
pull_request:
workflow_call:
# Exercise the floating daemon/vault test rigs daily; linked crates are pinned.
schedule:
- cron: "0 6 * * *"
concurrency:
# The event is part of the group, so a master push and the nightly run on master never
# cancel each other. Pull requests and train branches share one group per ref and cancel a
# superseded run. Every other run (master pushes, the nightly) gets a group of its own commit:
# GitHub keeps one pending run per group and drops an older pending one, so a shared master
# group would lose the result of a commit pushed between two others, and release tagging
# reads the CI result of the exact commit it tags.
group: ci-${{ github.event_name }}-${{ github.ref }}${{ (github.event_name != 'pull_request' && !startsWith(github.ref, 'refs/heads/train/')) && format('-{0}', github.sha) || '' }}
cancel-in-progress: ${{ github.event_name == 'pull_request' || startsWith(github.ref, 'refs/heads/train/') }}
env:
CARGO_TERM_COLOR: always
# Workflow-wide, not per step: any step that runs a script importing a shared module under
# scripts/lib/ leaves a __pycache__ there, and the clean-worktree check at the end rightly
# refuses it. Set once here, so a new python step cannot forget it; a per-step setting
# is how the deploy-gate self-test slipped when scripts/lib/wal_gate.py arrived.
PYTHONDONTWRITEBYTECODE: "1"
# Line tables keep panics and backtraces readable while cutting the debug target to a
# fraction of its size. Full debuginfo filled a GitHub-hosted Linux runner's disk and the
# linker died mid-build.
CARGO_PROFILE_DEV_DEBUG: line-tables-only
jobs:
fixtures:
uses: ./.github/workflows/fixtures.yml
lint:
name: Lint (${{ matrix.os }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu
runner: ubuntu-latest
- os: windows
runner: windows-latest
defaults:
run:
working-directory: broca
shell: bash
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
path: broca
- uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
with:
components: clippy, rustfmt
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
workspaces: broca -> target
shared-key: lint
cache-on-failure: 'true'
- name: fmt and clippy (same features as the tests)
run: |
source scripts/rust-test-gate.sh
run_phase fmt cargo fmt --all -- --check
run_phase clippy cargo clippy --locked --workspace --all-targets "${cargo_feature_args[@]}" -- -D warnings
- name: Verify clean worktree
run: test -z "$(git status --porcelain)" || { git status --short; git diff; exit 1; }
# Linux and Windows run broca-store-contention-tests in the workspace unit lane.
# Its VFS clock shim also needs native macOS coverage, not just developer test runs.
store-contention-macos:
name: Store contention (macos)
runs-on: macos-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
shared-key: store-contention
cache-on-failure: 'true'
- name: Deterministic SQLite writer gaps
run: cargo test --locked -p broca-store-contention-tests --lib
- name: Verify clean worktree
run: test -z "$(git status --porcelain)" || { git status --short; git diff; exit 1; }
scripts:
name: Operator scripts
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
- name: Train preconditions
run: python3 scripts/check-train-preconditions.py
- uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
shared-key: scripts
cache-on-failure: 'true'
# Dependency graph suites deliberately use --offline. Fetch their pinned
# sources here; an independent job cannot borrow another job's Cargo home.
- name: Prefetch locked dependencies for offline script checks
run: cargo fetch --locked
- name: Operator script suites and deploy/export self-tests
run: scripts/script-suites.sh
- name: Verify clean worktree
run: test -z "$(git status --porcelain)" || { git status --short; git diff; exit 1; }
wal-reader:
name: WAL reader (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
shared-key: wal-reader
cache-on-failure: 'true'
- name: Golden v4 codec and broca unlink while reader holds a log
shell: bash
run: |
cargo test --locked -p broca-wal-reader
cargo test --locked -p broca-wal --lib framing::tests::v4
- name: Verify clean worktree
shell: bash
run: test -z "$(git status --porcelain)" || { git status --short; git diff; exit 1; }
integration-build:
name: Archive (${{ matrix.os }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu
runner: ubuntu-latest
- os: windows
runner: windows-latest
defaults:
run:
working-directory: broca
shell: bash
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
path: broca
- uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
- uses: taiki-e/install-action@2eb6de229bc778f7df20e106e4b34e9a364c73e6 # nextest
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
id: cargo-cache
with:
workspaces: broca -> target
shared-key: archive
cache-on-failure: 'true'
- name: Compile and compare independent integration inventories
env:
NEXTEST_PROFILE: ci
run: bash scripts/ci-integration.sh build "$RUNNER_TEMP/integration.tar.zst"
# Asks nextest which tests the Linux preflight selects, so it runs here where nextest and
# the freshly built test binaries already exist, not in the operator scripts job.
- name: Linux preflight excludes every rig-dependent test
if: matrix.os == 'ubuntu'
run: PYTHONDONTWRITEBYTECODE=1 python3 scripts/test-portable-test-selection.py
- name: Cargo cache telemetry
run: echo "Archive cache exact hit = ${{ steps.cargo-cache.outputs.cache-hit }}" >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: integration-${{ matrix.os }}
path: ${{ runner.temp }}/integration.tar.zst
compression-level: 0
retention-days: 3
- name: Verify clean worktree
run: test -z "$(git status --porcelain)" || { git status --short; git diff; exit 1; }
integration:
name: Integration (${{ matrix.os }} ${{ matrix.shard }}/4)
# Prepare consumers alongside producers; each shard waits only for its own
# platform's artifacts, not the other platform's matrix jobs or cache saves.
runs-on: ${{ matrix.runner }}
# The artifact wait below may last as long as the slowest producer's own budget (45
# minutes, integration-build), and the replay needs its own time after that. A smaller job
# budget lets GitHub cancel a shard whose producer is still inside its allowance.
# scripts/test-ci-pipeline.py checks these three numbers against each other.
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
include:
- os: ubuntu
runner: ubuntu-latest
- os: windows
runner: windows-latest
os: [ubuntu, windows]
defaults:
run:
working-directory: broca
shell: bash
env:
BROCA_REQUIRE_SUBC: '1'
CK_BUS_TESTKIT_REQUIRE: '1'
BROCA_REQUIRE_AIMOCK: '1'
BROCA_REQUIRE_VAULT: ${{ matrix.os == 'ubuntu' && '1' || '' }}
SUBC_CORE_BIN: ${{ github.workspace }}/fixtures/bin/ck-subc${{ matrix.os == 'windows' && '.exe' || '' }}
FAKE_AFT_STUB_BIN: ${{ github.workspace }}/fixtures/bin/fake-aft-stub${{ matrix.os == 'windows' && '.exe' || '' }}
FAKE_SURFACE_BIN: ${{ github.workspace }}/fixtures/bin/fake-surface${{ matrix.os == 'windows' && '.exe' || '' }}
CK_CLAUSTRUM_BIN: ${{ matrix.os == 'ubuntu' && format('{0}/fixtures/bin/ck-claustrum', github.workspace) || '' }}
CK_AUTH_BIN: ${{ matrix.os == 'ubuntu' && format('{0}/fixtures/bin/ck-auth', github.workspace) || '' }}
CK_CLAUSTRUM_CHECKOUT: ${{ matrix.os == 'ubuntu' && format('{0}/fixtures/claustrum', github.workspace) || '' }}
NEXTEST_TEST_THREADS: num-cpus
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
path: broca
- uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
- uses: taiki-e/install-action@2eb6de229bc778f7df20e106e4b34e9a364c73e6 # nextest
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
- run: npm ci
- name: Install pinned private NATS broker
run: python3 scripts/install-ci-nats.py
- name: Wait for this platform's fixtures and test archive
env:
GH_TOKEN: ${{ github.token }}
run: python3 -B scripts/wait-ci-artifacts.py --timeout 2700 "fixtures-${{ runner.os }}" "integration-${{ matrix.os }}"
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: fixtures-${{ runner.os }}
path: ${{ runner.temp }}/fixtures
- run: bash scripts/unpack-fixtures.sh "$RUNNER_TEMP/fixtures/fixtures-$RUNNER_OS.tar.gz" "$GITHUB_WORKSPACE"
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: integration-${{ matrix.os }}
path: ${{ runner.temp }}/archive
- name: Replay integration shard from archive (no compilation)
run: bash scripts/ci-integration.sh run "$RUNNER_TEMP/archive/integration.tar.zst" "${{ matrix.shard }}/4"
- name: Upload process cleanup diagnostics
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: rig-survivors-integration-${{ matrix.os }}-${{ matrix.shard }}
path: broca/target/rig-survivors/*.txt
if-no-files-found: ignore
- name: Verify clean worktree
run: test -z "$(git status --porcelain)" || { git status --short; git diff; exit 1; }
linux-deploy:
name: Real Linux ELF deployment
needs: fixtures
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: broca
shell: bash
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
path: broca
- uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
workspaces: broca -> target
shared-key: release-deploy
cache-on-failure: 'true'
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: fixtures-Linux
path: ${{ runner.temp }}/fixtures
- run: bash scripts/unpack-fixtures.sh "$RUNNER_TEMP/fixtures/fixtures-Linux.tar.gz" "$GITHUB_WORKSPACE"
- name: Real Linux ELF deployment under subc
env:
SUBC_CORE_BIN: ${{ github.workspace }}/fixtures/bin/ck-subc
BROCA_DEPLOY_TEST_BIN: ${{ github.workspace }}/broca/target/release/ck-broca
BROCA_REQUIRE_LINUX_DEPLOY: '1'
# Build the same three binaries stage-release.sh builds: its publication sequence stages
# broca-session and broca-run from target/release beside the module.
run: |
cargo build --release --locked -p broca-module-serve -p broca-run --bin ck-broca --bin broca-session --bin broca-run
python3 -B scripts/test-place-release-gate.py LinuxDeployTests.test_real_linux_subc_stage_place_restart_verify -v
- name: Verify clean worktree
run: test -z "$(git status --porcelain)" || { git status --short; git diff; exit 1; }
# Every other Rust lane builds with broca-module-serve/test-clock, which turns on the
# controllable clock in broca-core and broca-module, so the code behind
# cfg(not(feature = "test-clock")) -- what a release runs -- was compiled by the release build
# but never linted or tested. This lane lints and tests those crates without it. It also
# checks the workspace on the oldest compiler Cargo.toml's rust-version promises; the other
# lanes use the newest stable.
production-features:
name: Production features and MSRV
needs: fixtures
runs-on: ubuntu-latest
timeout-minutes: 60
defaults:
run:
working-directory: broca
shell: bash
env:
BROCA_REQUIRE_SUBC: '1'
BROCA_REQUIRE_AIMOCK: '1'
BROCA_REQUIRE_VAULT: '1'
SUBC_CORE_BIN: ${{ github.workspace }}/fixtures/bin/ck-subc
FAKE_AFT_STUB_BIN: ${{ github.workspace }}/fixtures/bin/fake-aft-stub
FAKE_SURFACE_BIN: ${{ github.workspace }}/fixtures/bin/fake-surface
CK_CLAUSTRUM_BIN: ${{ github.workspace }}/fixtures/bin/ck-claustrum
CK_AUTH_BIN: ${{ github.workspace }}/fixtures/bin/ck-auth
CK_CLAUSTRUM_CHECKOUT: ${{ github.workspace }}/fixtures/claustrum
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
path: broca
- uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
with:
components: clippy
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
- run: npm ci
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: fixtures-Linux
path: ${{ runner.temp }}/fixtures
- run: bash scripts/unpack-fixtures.sh "$RUNNER_TEMP/fixtures/fixtures-Linux.tar.gz" "$GITHUB_WORKSPACE"
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
workspaces: broca -> target
shared-key: production-features
cache-on-failure: 'true'
- name: Clippy without test-clock
run: cargo clippy --locked -p broca-core -p broca-module -p broca-module-serve --all-targets -- -D warnings
- name: Lib/bin tests without test-clock
run: cargo test --locked -p broca-core -p broca-module -p broca-module-serve --lib --bins --no-fail-fast
- uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable branch, given the MSRV below
with:
toolchain: '1.96'
- name: Workspace check on the MSRV
run: cargo +1.96 check --locked --workspace --all-targets
- name: Verify clean worktree
run: test -z "$(git status --porcelain)" || { git status --short; git diff; exit 1; }
# Preserve the required status names, and refuse a skipped/red dependency.
test:
name: Test (${{ matrix.os }})
if: always()
needs: [lint, scripts, unit, production-features, store-contention-macos, integration-build, fixtures, integration, linux-deploy, wal-reader]
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu, windows]
steps:
- name: Require every lane
env:
NEEDS_JSON: ${{ toJSON(needs) }}
run: |
python3 - <<'PY'
import json, os, sys
lanes = json.loads(os.environ['NEEDS_JSON'])
print(f"Inspected {len(lanes)} required lanes: {lanes}")
if not lanes or any(lane['result'] != 'success' for lane in lanes.values()):
sys.exit('A required lane failed, was cancelled, or was skipped')
PY
# Linux + Windows matrix. Windows is the load-bearing leg: the cfg(windows) paths
# (e.g. the lease's ERROR_LOCK_VIOLATION contention check, the subc-binary .exe
# suffix) cannot be exercised by local macOS/Linux development. The full workspace
# matrix omits macOS; the targeted store-contention job above covers its VFS shim.
#
# Every job runs on GitHub-hosted runners.
unit:
needs: fixtures
name: Lib/bin/doc (${{ matrix.os }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu
runner: ubuntu-latest
- os: windows
runner: windows-latest
defaults:
run:
working-directory: broca
steps:
- name: Checkout broca
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
path: broca
- uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable branch
with:
components: clippy, rustfmt
# The provider-family e2e tests boot the pinned @copilotkit/aimock devDep (a
# Node mock LLM server) from node_modules, which is gitignored. Without it the
# tests can't boot aimock: most skip, but stream_errors_e2e requires it and
# fails — so the whole mock matrix only runs for real once `npm ci` installs it.
- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
- name: Install aimock (npm ci)
run: npm ci
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: fixtures-${{ runner.os }}
path: ${{ runner.temp }}/fixtures
- name: Unpack pinned fixtures
shell: bash
run: bash scripts/unpack-fixtures.sh "$RUNNER_TEMP/fixtures/fixtures-$RUNNER_OS.tar.gz" "$GITHUB_WORKSPACE"
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
id: cargo-cache
with:
workspaces: broca -> target
shared-key: units
cache-on-failure: 'true'
- name: Cargo cache telemetry
shell: bash
run: echo "Unit cache exact hit = ${{ steps.cargo-cache.outputs.cache-hit }}" >> "$GITHUB_STEP_SUMMARY"
- name: Check linked dependency sources
run: python3 scripts/check-path-deps.py && python3 scripts/test-check-path-deps.py
shell: bash
- name: Lib/bin tests and doctests (live and test-clock)
timeout-minutes: 45
env:
BROCA_REQUIRE_SUBC: "1"
BROCA_REQUIRE_AIMOCK: "1"
BROCA_REQUIRE_VAULT: ${{ matrix.os == 'ubuntu' && '1' || '' }}
SUBC_CORE_BIN: ${{ github.workspace }}/fixtures/bin/ck-subc${{ runner.os == 'Windows' && '.exe' || '' }}
FAKE_AFT_STUB_BIN: ${{ github.workspace }}/fixtures/bin/fake-aft-stub${{ runner.os == 'Windows' && '.exe' || '' }}
FAKE_SURFACE_BIN: ${{ github.workspace }}/fixtures/bin/fake-surface${{ runner.os == 'Windows' && '.exe' || '' }}
CK_CLAUSTRUM_BIN: ${{ matrix.os == 'ubuntu' && format('{0}/fixtures/bin/ck-claustrum', github.workspace) || '' }}
CK_AUTH_BIN: ${{ matrix.os == 'ubuntu' && format('{0}/fixtures/bin/ck-auth', github.workspace) || '' }}
CK_CLAUSTRUM_CHECKOUT: ${{ matrix.os == 'ubuntu' && format('{0}/fixtures/claustrum', github.workspace) || '' }}
shell: bash
run: |
source scripts/rust-test-gate.sh
run_phase 'lib/bin tests' cargo test --locked --workspace --lib --bins --no-fail-fast "${cargo_feature_args[@]}" --quiet
doc_count="$(count_doc_tests)"
echo "doctests discovered: $doc_count"
if [[ "$doc_count" -gt 0 ]]; then
run_phase 'doctests' cargo test --locked --workspace --doc "${cargo_feature_args[@]}" --quiet
fi
- name: Upload process cleanup diagnostics
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: rig-survivors-unit-${{ matrix.os }}
path: broca/target/rig-survivors/*.txt
if-no-files-found: ignore
# Exercised 2026-08-12 against a scratch repo, because this gate had been green on
# every run since the repo existed and nothing showed it COULD go red:
# clean tree -> passes
# modified tracked file -> fails, naming it
# untracked file left behind -> fails, naming it
# The third case is the one worth protecting and it rests on a default: porcelain
# reports untracked files, so a build that emits a generated source nobody added trips
# this. Adding --untracked-files=no while tidying would silently remove exactly that
# case, and the remaining two would keep passing.
# The diff is EMPTY for an untracked file, so it is printed conditionally: an
# unconditional `git diff` under a porcelain listing makes the listing look redundant,
# and the listing is the only output naming the file.
- name: Verify clean worktree
shell: bash
run: |
dirty="$(git status --porcelain)"
if [ -n "$dirty" ]; then
echo "::error::Working tree changed during CI (modified tracked files, or new files a build left behind)"
printf '%s\n' "$dirty"
diff_out="$(git diff -- . | head -c 20000)"
if [ -n "$diff_out" ]; then
echo "git diff (first 20000 bytes):"
printf '%s\n' "$diff_out"
else
echo "(no diff: the change is untracked files the build left behind -- see the list above)"
fi
exit 1
fi