-
-
Notifications
You must be signed in to change notification settings - Fork 25
534 lines (447 loc) · 22.1 KB
/
Copy pathci.yml
File metadata and controls
534 lines (447 loc) · 22.1 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
526
527
528
529
530
531
532
533
534
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_call:
# CI executes repository code and has no consumer of checks-write permission.
# Checkout credentials must not remain available to build scripts or tests.
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
RG_VERSION: 15.1.0
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Clippy without default features (lean build, no bash_tool)
run: cargo clippy -p bashkit --no-default-features --lib --tests -- -D warnings
- name: Build documentation
run: cargo doc --no-deps --all-features
env:
RUSTDOCFLAGS: "-D warnings"
# OKF v0.2 conformance of the knowledge bundle. okf-lint enforces the
# spec; check_okf.py enforces the bundle-local conventions it does not
# cover. See knowledge/knowledge-contract.md for why both run.
# Keep the version and --max-line-length in lockstep with the justfile.
- name: Install okf-lint
run: cargo install okf-lint --version 0.1.1 --locked
- name: Check knowledge bundle (OKF)
run: |
python3 scripts/check_okf.py knowledge
okf-lint knowledge --max-line-length 10000
# The site rewrites doc links by basename, so a cross-tree link written as
# a bare `jq.md` renders fine on bashkit.sh while 404-ing for anyone
# reading the markdown on GitHub. site/scripts/verify-doc-*.mjs check the
# built routes and cannot see this; these check the source-relative form.
- name: Check doc links
run: python3 scripts/check_doc_links.py
- name: Test repo scripts
run: python3 -m unittest discover -s scripts/tests -p 'test_*.py'
wasm:
# std::time panics on wasm32-unknown-unknown, so bashkit routes clock reads
# through src/time_compat.rs (web-time on wasm32). This job keeps the crate
# compiling for browser/wasm-bindgen consumers; the feature list is the
# supported wasm32-unknown-unknown surface (python is excluded: monty pulls
# getrandom 0.3 without its wasm_js feature).
name: WASM (wasm32-unknown-unknown, wasm32-wasip2)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
with:
targets: wasm32-unknown-unknown, wasm32-wasip2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Check bashkit for wasm32-unknown-unknown
run: cargo check -p bashkit --target wasm32-unknown-unknown --features scripted_tool,jq,wasm_js
env:
# getrandom needs an explicit JS backend opt-in on this target.
RUSTFLAGS: --cfg getrandom_backend="wasm_js" -D warnings
# WASI keeps the crate usable in non-JS wasm runtimes (wasmtime, and the
# wasmtime-in-a-micro-VM guest of hyperlight-wasm). Same feature surface
# as the browser package; no getrandom cfg here (wasip2 has a native
# backend). Check-only: there is no WASI entry-point crate yet.
- name: Check bashkit for wasm32-wasip2
run: cargo check -p bashkit --target wasm32-wasip2 --features scripted_tool,jq
env:
RUSTFLAGS: -D warnings
wasm-component:
# The non-JS wasm surface: build bashkit as a component with no JS and no
# WASI imports (the shape a Hyperlight micro-VM guest needs) and run a
# script through it under wasmtime. See knowledge/runtimes/non-js-wasm.md.
# Guards the `wasm_js`-off configuration, which no other job covers: a
# stray JS-backed dependency only shows up as an unresolvable import here.
name: WASM component (no JS, no WASI)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Install wasm-tools
run: cargo install wasm-tools --version 1.256.0 --locked
- name: Build component and run a script through it
run: ./examples/hyperlight/build-and-run.sh
wasm-web:
# Build the browser package (@everruns/bashkit-wasm) and run its headless
# integration tests, proving the single-threaded bundle loads with no
# COOP/COEP headers and that sync + async execution both work. See
# knowledge/runtimes/browser-package.md. Keep wasm-bindgen-cli in lockstep with the
# wasm-bindgen version in Cargo.lock.
name: WASM web package
runs-on: ubuntu-latest
# A hung install step here took `main` red twice by burning the 6-hour job
# ceiling (see scripts/install-binaryen.sh). This job's slowest healthy run
# is well under 10 minutes, so anything past 30 is stuck, not slow — fail
# fast and leave a re-runnable red instead of a cancelled run.
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Install wasm-bindgen-cli
run: bash scripts/install-wasm-bindgen.sh
- name: Install binaryen (wasm-opt)
run: sudo ./scripts/install-binaryen.sh
- name: Build web bundle
run: bash crates/bashkit-wasm/scripts/build.sh release
- name: Integration tests (headless Node)
run: node --test "crates/bashkit-wasm/__test__/*.test.mjs"
audit:
name: Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
# Install the prebuilt cargo-audit binary rather than letting
# rustsec/audit-check compile it from source. The source build resolves
# cargo-audit's own dependencies unlocked, and a newer `kstring` bumped
# its MSRV past our pinned toolchain (rust-toolchain.toml forces the
# override even for `cargo install`), which broke this job. A prebuilt
# binary is immune to future tool-dependency MSRV bumps.
- name: Install cargo-audit
uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2
with:
tool: cargo-audit
- name: Security audit (cargo-audit)
# Lockfile discovery and the advisory suppression list live in the
# script, which the nightly `advisories` job runs too. Keeping them
# inline here meant the second caller would need a second copy.
run: ./scripts/audit-lockfiles.sh
- name: License check (cargo-deny)
# Held at v2.0.20: v2.1.0 (6f99e34) bundles cargo-deny 0.20.2, whose
# CLI no longer accepts the `--log-level warn` the action still injects,
# breaking `check licenses sources`. Re-bump once upstream reconciles.
uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2.1.1
with:
command: check licenses sources
- name: Install cargo-vet
uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2
with:
tool: cargo-vet
- name: Supply chain audit (cargo-vet)
run: cargo vet --locked
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
# Pre-installed on `ubuntu-latest` images, but make the dependency
# explicit so a future runner image change can't silently turn the
# `sqlite_differential_tests` suite into a no-op (it skips when
# `sqlite3` isn't on PATH).
- name: Install host sqlite3 for differential tests
run: |
if ! command -v sqlite3 >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y sqlite3
fi
sqlite3 --version
# Install the uutils multicall binary for
# `coreutils_differential_tests`. The harness gracefully skips when
# the binary is unavailable, so `continue-on-error` keeps the
# regular Test job green when the upstream install action has a
# bad day — the drift workflow (`coreutils-args-drift.yml`) is the
# authoritative body-drift gate, building uutils from the pinned
# tree before running the harness.
- name: Install uutils coreutils multicall for differential tests
id: install_uutils
continue-on-error: true
uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2
with:
tool: coreutils
- name: Verify uutils on PATH
if: steps.install_uutils.outcome == 'success'
run: coreutils --version
# `rg` differential tests compare against real ripgrep. Pin the
# binary so upstream formatting/path semantics cannot drift under CI.
- name: Install pinned ripgrep for differential tests
run: scripts/install-ripgrep-ci.sh "$RG_VERSION"
# Examples have a dedicated job below; skipping them here avoids
# duplicated example links that can exhaust runner disk on main.
- name: Run tests
run: cargo test --workspace --lib --bins --tests --features http_client,ssh,sqlite
- name: Run strict bash parity tests
run: cargo test -p bashkit --test integration --features http_client,ssh -- spec_tests::bash_comparison_tests --ignored
- name: Run competitor regression corpus
run: cargo test -p bashkit --test integration --features http_client,jq -- competitor_regression_tests
- name: Run doc tests
run: cargo test --workspace --doc --features http_client,ssh,sqlite
- name: Run realfs tests
run: cargo test --features realfs -p bashkit --test realfs_tests -p bashkit-cli
# The third-party adoption and host-mount suites are `cfg(realfs)`, so
# the main "Run tests" step (no realfs) compiles them out entirely.
- name: Run realfs integration tests
run: cargo test -p bashkit --test integration --features realfs -- host_mounts_tests thirdparty_adoption_tests
- name: Run fail-point tests (single-threaded)
run: cargo test --features failpoints --test security_failpoint_tests -- --test-threads=1
- name: Run property-based security tests (proptest)
run: cargo test --test proptest_security -- --test-threads=1
env:
PROPTEST_CASES: 50
windows-containment:
# THREAT[TM-ESC-033]: Windows path parsing has drive, UNC, device, and
# reparse-point semantics that cannot be exercised on a Unix runner.
name: Windows filesystem containment
runs-on: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
shared-key: windows-containment
- name: Test VFS path normalization
run: cargo test -p bashkit --lib --features realfs windows_containment
- name: Test RealFS and overlay containment
run: cargo test -p bashkit --test realfs_tests --features realfs windows_containment
- name: Test touch mtime on Windows (issue #2388)
run: cargo test -p bashkit --test realfs_tests --features realfs realfs_touch
# The adoption shape this covers — bashkit as an agent's shell, with commands
# bridged to host processes — exists to work on Windows without a real bash.
# Kept separate from `windows-containment`: that job pins path-containment
# semantics (TM-ESC-033), this one pins the embedding contract. Scoped to the
# adoption suites rather than the whole workspace.
test-windows:
name: Test (Windows adoption shape)
runs-on: windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
shared-key: windows-adoption
- name: Run third-party adoption tests
run: cargo test -p bashkit --test integration --features realfs -- thirdparty_adoption_tests
- name: Run host mount mapping tests
run: cargo test -p bashkit --test integration --features realfs -- host_mounts_tests
- name: Run command resolver tests
run: cargo test -p bashkit --test integration -- command_resolver_tests
examples:
name: Examples
runs-on: ubuntu-latest
# Secret guard: only expose Doppler/API keys on push to main (trusted
# code). PRs — including same-repo branches — execute PR-controlled
# code (examples, build.rs, scripts) and must never see secrets.
# `github.event_name == 'push'` is the only reliable gate; the old
# `head.repo.fork != true` check passes for same-repo PRs and for
# workflow_call events where pull_request context is absent.
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
- name: Build examples
run: cargo build --examples --features "git,http_client,ssh,sqlite"
- name: Run C API examples
run: ./scripts/run-c-api-examples.sh
- name: Run examples
run: |
cargo run --example basic
cargo run --example custom_fs
cargo run --example clap_builtin
cargo run --example clap_builtin_subcommands
cargo run --example scripted_tool_clap_builtin --features scripted_tool
cargo run --example resource_limits
cargo run --example text_processing
cargo run --example live_mounts
cargo run --example git_workflow --features git
cargo run --example python_external_functions --features python
cargo run --example typescript_external_functions --features typescript
cargo run --example realfs_readonly --features realfs
cargo run --example realfs_readwrite --features realfs
cargo run --example sqlite_basic --features sqlite
cargo run --example sqlite_workflow --features sqlite
# SSH tests
- name: Run ssh builtin tests (mock handler)
run: cargo test --features ssh -p bashkit --test ssh_builtin_tests
- name: Run ssh supabase.sh example and tests
run: |
if nc -z -w 5 supabase.sh 22 2>/dev/null; then
cargo run --example ssh_supabase --features ssh
cargo test --features ssh -p bashkit --test ssh_supabase_tests
else
echo "::warning::supabase.sh:22 unreachable from this runner — skipping live SSH tests"
fi
- name: Run realfs bash example
run: |
cargo build -p bashkit-cli --features realfs
bash examples/realfs_mount.sh
- name: Run ticket CLI example
env:
TICKET_REF: 194b71a8bbc3771da1ce9f579395937c976bbddc
run: bash examples/ticket-cli.sh
# examples/browser ships its own suite: unit tests for the browser-local
# helper, plus a reproducibility guard asserting every dependency is
# pinned to an exact version alongside a committed lockfile.
#
# Important decision: it runs here rather than in js.yml. The suite is
# plain `node --test` with no install step (its fakes replace the wasm
# package), so it costs ~1s, while js.yml both builds the napi binding
# and filters on paths that exclude examples/browser — the guard would
# not fire on the package.json edits it exists to catch. Nothing ran this
# suite before, which is how `vite` drifted to a caret range unnoticed
# and stayed there across a two-major bump.
- name: Run browser example tests
working-directory: examples/browser
# `node --test` exits 0 when its argument matches no file, so a renamed
# or moved suite would report green having run nothing. Assert the
# files exist first.
run: |
ls ./*.test.js
node --test ./*.test.js
# Detect whether DOPPLER_TOKEN is configured. Only set on push to
# main (trusted code); PRs — including same-repo branches — must
# never see the secret. Subsequent Doppler steps gate on
# `env.DOPPLER_AVAILABLE` so this job stays green when unconfigured.
- name: Detect Doppler availability (trusted runs only)
if: github.event_name == 'push'
run: echo "DOPPLER_AVAILABLE=${{ secrets.DOPPLER_TOKEN != '' }}" >> "$GITHUB_ENV"
- name: Install Doppler CLI
if: github.event_name == 'push' && env.DOPPLER_AVAILABLE == 'true'
uses: dopplerhq/cli-action@4819d808ab99e5cde19a0637a16536a4038fad73 # v4
- name: Build LLM examples without credentials
run: |
cargo build --example agent_tool --features http_client
cargo build -p bashkit-cli --features realfs --quiet
# Resolve only the required key; this token-bearing process exits before
# repository code runs. Mask the output before crossing the step boundary.
- name: Fetch Anthropic example key
id: anthropic
continue-on-error: true
if: github.event_name == 'push' && env.DOPPLER_AVAILABLE == 'true'
shell: bash
env:
DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }}
run: |
key=$(doppler secrets get ANTHROPIC_API_KEY --plain)
printf '::add-mask::%s\n' "$key"
printf 'ANTHROPIC_API_KEY=%s\n' "$key" >> "$GITHUB_OUTPUT"
# External API availability must not block ordinary repository checks.
- name: Run LLM agent example
if: github.event_name == 'push' && env.DOPPLER_AVAILABLE == 'true' && steps.anthropic.outcome == 'success'
continue-on-error: true
env:
ANTHROPIC_API_KEY: ${{ steps.anthropic.outputs.ANTHROPIC_API_KEY }}
run: target/debug/examples/agent_tool
# Resolve only the required key; this token-bearing process exits before
# repository code runs. Mask the output before crossing the step boundary.
- name: Fetch OpenAI example key
id: openai
continue-on-error: true
if: github.event_name == 'push' && env.DOPPLER_AVAILABLE == 'true'
shell: bash
env:
DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN }}
run: |
key=$(doppler secrets get OPENAI_API_KEY --plain)
printf '::add-mask::%s\n' "$key"
printf 'OPENAI_API_KEY=%s\n' "$key" >> "$GITHUB_OUTPUT"
- name: Run harness OpenAI joke example
if: github.event_name == 'push' && env.DOPPLER_AVAILABLE == 'true' && steps.openai.outcome == 'success'
continue-on-error: true
env:
OPENAI_API_KEY: ${{ steps.openai.outputs.OPENAI_API_KEY }}
run: bash examples/harness-openai-joke.sh
fuzz-check:
name: Fuzz Compile Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
- name: Install cargo-fuzz
uses: taiki-e/cache-cargo-install-action@9ee83daaa7b96a6fab930949ecf1122bba04a389 # v3
with:
tool: cargo-fuzz
locked: true
- name: Verify fuzz targets compile
working-directory: crates/bashkit
run: cargo +nightly fuzz build
# Gate job for branch protection — name must stay "Check"
check:
name: Check
if: always()
needs: [lint, audit, test, windows-containment, test-windows, examples, fuzz-check, wasm, wasm-component, wasm-web]
runs-on: ubuntu-latest
steps:
- name: Verify all jobs passed
run: |
if [[ "${{ needs.lint.result }}" != "success" ]] || \
[[ "${{ needs.audit.result }}" != "success" ]] || \
[[ "${{ needs.test.result }}" != "success" ]] || \
[[ "${{ needs.windows-containment.result }}" != "success" ]] || \
[[ "${{ needs.test-windows.result }}" != "success" ]] || \
[[ "${{ needs.examples.result }}" != "success" ]] || \
[[ "${{ needs.fuzz-check.result }}" != "success" ]] || \
[[ "${{ needs.wasm.result }}" != "success" ]] || \
[[ "${{ needs.wasm-component.result }}" != "success" ]] || \
[[ "${{ needs.wasm-web.result }}" != "success" ]]; then
echo "One or more required jobs failed"
exit 1
fi