Skip to content

Commit 070390d

Browse files
committed
Merge branch 'main' into lab-1040-release-please-hosted
2 parents 928fa95 + cfb0a98 commit 070390d

11 files changed

Lines changed: 634 additions & 95 deletions

File tree

‎.github/scripts/assert_hosted_runners.py‎

Lines changed: 456 additions & 0 deletions
Large diffs are not rendered by default.

‎.github/workflows/attestation-check.yml‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ concurrency:
1414
jobs:
1515
verify:
1616
name: Verify Latest Release Attestations
17-
# ubuntu-latest, NOT the self-hosted `cachekit` runner: the ARC pods have no `gh`
18-
# binary (LAB-899) and unreliable Sigstore (Fulcio/Rekor) egress — the same reason
19-
# release.yml's publish job is hosted. `gh attestation verify` is the only tool
20-
# that does Sigstore bundle verification, so moving this job back to ARC silently
21-
# re-breaks it (LAB-984: every `gh` call exited 127 into `|| echo ""`, and the job
22-
# reported green while verifying nothing for weeks).
17+
# Must stay GitHub-hosted: this job needs `gh` (preinstalled on ubuntu-latest)
18+
# and reliable Sigstore (Fulcio/Rekor) egress. `gh attestation verify` is the
19+
# only tool that does Sigstore bundle verification. The whole repo is on
20+
# ubuntu-latest (LAB-3501) and runner-guard.yml keeps it there, but the point
21+
# is load-bearing here specifically: when this once ran on a self-hosted runner
22+
# with no `gh` binary, every `gh` call exited 127 into `|| echo ""` and
23+
# the job reported green while verifying nothing for weeks.
2324
runs-on: ubuntu-latest
2425
permissions:
2526
contents: read

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,31 @@ concurrency:
88
group: ${{ github.workflow }}-${{ github.ref }}
99
cancel-in-progress: true
1010

11-
env:
12-
# cargo's available_parallelism() reads the node's 32 threads, not the pod's
13-
# cgroup CPU quota, so a cold (cache-miss) build fans out ~22-way and can
14-
# OOM-kill the linker in the 5Gi cachekit runner — the same failure that hit
15-
# cachekit-rs (#25/#26). Cap parallel jobs so peak RSS stays under the cap.
16-
CARGO_BUILD_JOBS: "4"
17-
1811
jobs:
1912
test:
2013
name: ${{ matrix.rust }} / ${{ matrix.os }}
21-
runs-on: ${{ matrix.runner }}
14+
runs-on: ${{ matrix.os }}
2215
# Fail fast instead of hanging ~10min to GitHub's heartbeat if a runner wedges.
2316
timeout-minutes: 20
2417
continue-on-error: ${{ matrix.rust == 'beta' }}
2518
strategy:
2619
fail-fast: false
2720
matrix:
28-
# Full OS matrix for stable only; MSRV and beta on self-hosted only
21+
# Full OS matrix for stable only; MSRV and beta on Linux only.
2922
include:
3023
# MSRV - ensures we don't use newer Rust features
3124
- rust: "1.85"
3225
os: ubuntu-latest
33-
runner: cachekit
3426
# Stable - primary target, all platforms
3527
- rust: stable
3628
os: ubuntu-latest
37-
runner: cachekit
3829
- rust: stable
3930
os: macos-latest
40-
runner: macos-latest
4131
- rust: stable
4232
os: windows-latest
43-
runner: windows-latest
4433
# Beta - early warning (allowed to fail)
4534
- rust: beta
4635
os: ubuntu-latest
47-
runner: cachekit
4836
steps:
4937
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
5038

@@ -76,7 +64,7 @@ jobs:
7664
run: cargo test --features ffi
7765

7866
security:
79-
runs-on: cachekit
67+
runs-on: ubuntu-latest
8068
timeout-minutes: 20
8169
steps:
8270
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

‎.github/workflows/codeql.yml‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,10 @@ concurrency:
1313
group: ${{ github.workflow }}-${{ github.ref }}
1414
cancel-in-progress: true
1515

16-
env:
17-
# cargo's available_parallelism() reads the node's 32 threads, not the pod's
18-
# cgroup CPU quota, so the cold release build below can OOM-kill the linker in
19-
# the 5Gi cachekit runner (cachekit-rs #25/#26). Cap parallel jobs to fit.
20-
CARGO_BUILD_JOBS: "4"
21-
2216
jobs:
2317
analyze:
2418
name: Analyze
25-
runs-on: cachekit
19+
runs-on: ubuntu-latest
2620
timeout-minutes: 30
2721
permissions:
2822
actions: read

‎.github/workflows/release.yml‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ concurrency:
2121

2222
jobs:
2323
# GitHub-hosted: this job inherits contents+PR write and mints an App
24-
# installation token. The self-hosted `cachekit` pool shares a writable
25-
# hostPath build cache (/cache) across all pods and every cachekit-io repo,
26-
# and job-level `permissions:` does not isolate that filesystem — untrusted
27-
# build-script/proc-macro code can poison the cache a later credentialed
28-
# job runs against (LAB-1040). release-please needs no warm cache anyway.
24+
# installation token, so it must not share a writable build cache with other
25+
# jobs — job-level `permissions:` does not isolate a filesystem, and untrusted
26+
# build-script/proc-macro code could poison a cache a later credentialed job
27+
# runs against. A fresh hosted VM has no shared cache to poison, and
28+
# release-please needs no warm cache anyway.
2929
release-please:
3030
runs-on: ubuntu-latest
3131
outputs:
@@ -47,8 +47,8 @@ jobs:
4747
# release PR here. outputs.pr is set whenever the release PR was created
4848
# OR updated, so this re-runs on every push to main while a release PR is
4949
# open — adding an already-present assignee is a no-op, so that's safe.
50-
# github-script, NOT `gh`: chosen when this job ran on the self-hosted
51-
# cachekit runner, which has no gh CLI (LAB-899); it works identically on
50+
# github-script, NOT `gh`: chosen when this job ran on a self-hosted
51+
# runner without the gh CLI; it works identically on
5252
# a hosted runner. outputs.pr is passed raw via env and parsed in JS — never
5353
# through template-position fromJson, which is evaluated even when if: is
5454
# false and crashes on '' for no-release pushes (LAB-865).

‎.github/workflows/runner-guard.yml‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Runner Guard
2+
3+
# Drift protection (LAB-3501): every job in this repo must run on a GitHub-hosted
4+
# runner. This job fails the workflow if any `runs-on` or matrix `os:`/`runner:`
5+
# value in .github/workflows/ is not a hosted label — an allow-list, so a NEW
6+
# self-hosted label nobody has named yet still fails.
7+
#
8+
# This is protection against MAINTAINER drift on trusted branches only. It is NOT a
9+
# fork-PR control: a fork runs its own copy of this file and can delete the guard;
10+
# the control for that lives in repository and org runner settings, outside this file.
11+
12+
on:
13+
push:
14+
branches: [main]
15+
pull_request:
16+
branches: [main]
17+
18+
permissions:
19+
contents: read
20+
21+
concurrency:
22+
group: ${{ github.workflow }}-${{ github.ref }}
23+
cancel-in-progress: true
24+
25+
jobs:
26+
assert-hosted-runners:
27+
name: Assert all runners are GitHub-hosted
28+
runs-on: ubuntu-latest
29+
timeout-minutes: 5
30+
steps:
31+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
32+
- name: Self-test the guard, then scan every workflow
33+
run: |
34+
python3 .github/scripts/assert_hosted_runners.py --selftest
35+
python3 .github/scripts/assert_hosted_runners.py

‎.github/workflows/security.yml‎

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ on:
77
branches: [main]
88
schedule:
99
# Saturday 11:07 UTC = Sat 21:07 AEST / 22:07 AEDT (Sydney night, year-round).
10-
# Weekly cadence: deep fuzz at 1h/target × 16 targets is ~16 runner-hours, which
11-
# drains in a few hours overnight instead of monopolising the shared ARC pool for
12-
# ~32h. The full 8h/target run is opt-in via workflow_dispatch (run_deep_fuzz).
13-
# PR-time coverage (cargo audit/deny, Cargo Vet, Quick Fuzz, CodeQL) catches
14-
# regressions promptly; deep fuzz is for finding bugs, not gating merges.
15-
# Off-minute (:07) avoids the cron pile-up that GitHub schedules at :00.
10+
# Weekly cadence: deep fuzz at 1h/target × 16 targets. Each matrix target is a
11+
# separate GitHub-hosted job, so they run in parallel (bounded by GitHub's
12+
# concurrency limit) rather than serialising on shared runners — and hosted minutes
13+
# are free for public repos. The longer opt-in run is via workflow_dispatch
14+
# (run_deep_fuzz). PR-time coverage (cargo audit/deny, Cargo Vet, Quick Fuzz,
15+
# CodeQL) catches regressions promptly; deep fuzz is for finding bugs, not
16+
# gating merges. Off-minute (:07) avoids the cron pile-up GitHub schedules at :00.
1617
- cron: '7 11 * * 6'
1718
# Deliberately no `release:` trigger. It existed only for an SBOM job that
1819
# attached a release asset, and that can never work here: immutable releases
@@ -29,13 +30,13 @@ on:
2930
workflow_dispatch:
3031
inputs:
3132
run_deep_fuzz:
32-
description: "Run the full deep-fuzz matrix (heavy — occupies the ARC pool)"
33+
description: "Run the full deep-fuzz matrix (heavy — one hosted job per target)"
3334
type: boolean
3435
default: false
3536
fuzz_seconds:
36-
description: "Seconds per target for an on-demand deep fuzz (default 8h)"
37+
description: "Seconds per target for an on-demand deep fuzz (default 5h; hard cap 18000s to fit GitHub's 6h job limit)"
3738
type: string
38-
default: "28800"
39+
default: "18000"
3940

4041
concurrency:
4142
group: ${{ github.workflow }}-${{ github.ref }}
@@ -44,16 +45,11 @@ concurrency:
4445
env:
4546
CARGO_TERM_COLOR: always
4647
RUST_BACKTRACE: 1
47-
# Cap cargo parallelism so cold builds (incl. ASAN fuzz-target builds) don't
48-
# OOM-kill the linker in the 5Gi cachekit runner — see cachekit-rs #25/#26.
49-
# NOTE: -j bounds parallel codegen, not ASAN's absolute footprint; it's a
50-
# strong mitigation for the fuzz builds, a full fix for normal cargo builds.
51-
CARGO_BUILD_JOBS: "4"
5248

5349
jobs:
5450
fast-security:
5551
name: Fast Security Checks
56-
runs-on: cachekit
52+
runs-on: ubuntu-latest
5753
if: github.event_name == 'push' || github.event_name == 'pull_request'
5854
steps:
5955
- name: Checkout code
@@ -98,7 +94,7 @@ jobs:
9894

9995
quick-fuzz:
10096
name: Quick Fuzz (Corpus Only)
101-
runs-on: cachekit
97+
runs-on: ubuntu-latest
10298
if: github.event_name == 'push' || github.event_name == 'pull_request'
10399
strategy:
104100
fail-fast: false
@@ -160,13 +156,13 @@ jobs:
160156
161157
deep-fuzz:
162158
name: Deep Fuzzing
163-
runs-on: cachekit
164-
# Scheduled weekly run is light (1h/target) so it can't monopolise the shared
165-
# ARC pool. The full 8h/target run is opt-in via workflow_dispatch (run_deep_fuzz).
159+
runs-on: ubuntu-latest
160+
# Scheduled weekly run is light (1h/target). The longer on-demand run is opt-in
161+
# via workflow_dispatch (run_deep_fuzz) and clamped to fit the hosted 6h cap.
166162
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.run_deep_fuzz)
167-
# 540min cap accommodates the on-demand 8h path; the inner timeout governs the
168-
# actual scheduled (1h) vs dispatched (configurable) duration.
169-
timeout-minutes: 540
163+
# 360min = GitHub's hosted job cap. The inner timeout governs the actual
164+
# scheduled (1h) vs dispatched (<=5h, clamped below) duration.
165+
timeout-minutes: 360
170166
strategy:
171167
fail-fast: false
172168
matrix:
@@ -214,18 +210,20 @@ jobs:
214210
run: cargo install cargo-fuzz
215211

216212
- name: Run deep fuzz
217-
# Scheduled runs use 1h/target (keeps the ARC pool free for PR CI); a manual
218-
# workflow_dispatch can request the full 8h (or any duration) via fuzz_seconds.
213+
# Scheduled runs use 1h/target; a manual workflow_dispatch can request a
214+
# longer duration (up to the 5h clamp below) via fuzz_seconds.
219215
env:
220216
FUZZ_SECONDS: ${{ (github.event_name == 'workflow_dispatch' && inputs.fuzz_seconds) || '3600' }}
221217
run: |
222218
# Validate the dispatch-supplied duration before it reaches shell arithmetic
223-
# and the fuzzer. Must be a positive integer and within the 540min job cap.
219+
# and the fuzzer. Must be a positive integer within the hosted 6h job cap.
220+
# Ceiling is 18000s (5h): +180s hang slack (below) still leaves headroom
221+
# under timeout-minutes 360 (21600s), so a dispatched job cannot exceed 6h.
224222
case "$FUZZ_SECONDS" in
225223
''|*[!0-9]*) echo "::error::fuzz_seconds must be a positive integer (got '$FUZZ_SECONDS')"; exit 1 ;;
226224
esac
227-
if [ "$FUZZ_SECONDS" -lt 1 ] || [ "$FUZZ_SECONDS" -gt 32400 ]; then
228-
echo "::error::fuzz_seconds must be 1..32400 (<= 540min job cap), got $FUZZ_SECONDS"; exit 1
225+
if [ "$FUZZ_SECONDS" -lt 1 ] || [ "$FUZZ_SECONDS" -gt 18000 ]; then
226+
echo "::error::fuzz_seconds must be 1..18000 (<= 6h hosted job cap), got $FUZZ_SECONDS"; exit 1
229227
fi
230228
cd fuzz
231229
# Build first - fail fast on compile errors
@@ -249,7 +247,7 @@ jobs:
249247

250248
kani:
251249
name: Kani Formal Verification
252-
runs-on: cachekit
250+
runs-on: ubuntu-latest
253251
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
254252
permissions:
255253
contents: read # least-privilege: the job only checks out and verifies
@@ -293,7 +291,7 @@ jobs:
293291

294292
cargo-vet:
295293
name: Cargo Vet (Supply Chain)
296-
runs-on: cachekit
294+
runs-on: ubuntu-latest
297295
if: github.event_name == 'schedule' || github.event_name == 'pull_request'
298296
steps:
299297
- name: Checkout code

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Rust build artifacts
22
/target/
33

4+
# Python bytecode from .github/scripts/ (the runner-guard drift check)
5+
__pycache__/
6+
*.py[cod]
7+
48
# Generated files (keep .gitkeep but ignore generated header)
59
include/cachekit.h
610

‎tests/dual_decode.rs‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@
2020
//! attached to the decision memo on that issue); throughput evidence lives in
2121
//! the LAB-510 harness (`benches/hot_path.rs`, 64 MiB incompressible group).
2222
//!
23-
//! Width boundaries: the fixture's `*_bin` twins all fit in a bin8 header
24-
//! (`0xc4`, ≤ 255 B). The `width_boundary_*` tests cover the bin16 (`0xc5`)
25-
//! and bin32 (`0xc6`) headers with real LZ4 output from the real writer, per
26-
//! the MUST recorded in the decision record.
23+
//! Width boundaries: the fixture pins both bin8 (`0xc4`) and bin16 (`0xc5`)
24+
//! twins. The `width_boundary_*` tests additionally cover the bin32 (`0xc6`)
25+
//! header with real LZ4 output from the real writer, per the decision record.
2726
2827
#![cfg(all(feature = "compression", feature = "checksum", feature = "messagepack"))]
2928

@@ -123,27 +122,29 @@ fn dual_decode_matrix_against_legacy_vectors() {
123122
v.name
124123
);
125124
assert_eq!(
126-
// 0xc4 = msgpack bin8. Every pinned twin's compressed_data is small
127-
// enough to be bin8; a wider marker here is a width-selection
128-
// regression that would break cross-SDK byte-identity.
129125
new_wire[1],
130-
0xc4,
131-
"[{}] compressed_data not bin8-encoded: 0x{:02x}",
126+
match new_from_old.compressed_data.len() {
127+
0..=255 => 0xc4,
128+
256..=65_535 => 0xc5,
129+
_ => 0xc6,
130+
},
131+
"[{}] compressed_data does not use the shortest bin marker: 0x{:02x}",
132132
v.name,
133133
new_wire[1]
134134
);
135135
// Documented micro-regression: bin costs at most +1 B, and only on the
136136
// smallest envelopes. A <=15 B compressed_data goes from a 1 B fixarray
137137
// header to a 2 B bin8 header (+1); any array >=16 B already carried a
138-
// 3 B array16 header, so bin8's 2 B header shrinks it. Measured worst
139-
// case on the pinned set is exactly +1 (`empty`, 25 -> 26 B); every
140-
// other vector ties or shrinks. This +1 B ceiling is the micro-
138+
// 3 B array16 header, so bin8's 2 B header shrinks it. The bin16 tier
139+
// uses the same three-byte header as array16, so it cannot grow either.
140+
// Measured worst case on the pinned set is exactly +1 (`empty`, 25 ->
141+
// 26 B); every other vector ties or shrinks. This +1 B ceiling is the micro-
141142
// regression contract accepted by the writer flip (LAB-764/LAB-866),
142143
// so keep it tight — a +2/+3 bloat regression must not slip through
143144
// green.
144145
assert!(
145146
new_wire.len() <= old_wire.len() + 1,
146-
"[{}] new wire exceeds the +1 B bin8-header ceiling",
147+
"[{}] new wire exceeds the +1 B bin-header ceiling",
147148
v.name
148149
);
149150

@@ -212,9 +213,8 @@ fn assert_width_tier(name: &str, payload_len: usize, expected_marker: u8) {
212213
assert_all_readers_decode(name, &wire, &payload);
213214
}
214215

215-
/// bin8/bin16 boundary: the fixture twins never leave bin8 territory (all
216-
/// compressed_data <= 255 B), so sweep real LZ4 outputs across the 255/256 B
217-
/// boundary and prove both header widths decode through every reader shape.
216+
/// bin8/bin16 boundary: the fixture pins one bin16 twin, and this sweep covers
217+
/// real LZ4 outputs across the 255/256 B boundary through every reader shape.
218218
/// LZ4 adds a few bytes of literal-run overhead on incompressible input, so
219219
/// the sweep brackets the boundary regardless of the exact overhead.
220220
#[test]

0 commit comments

Comments
 (0)