diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad4274b..a519435 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,6 +65,33 @@ jobs: working-directory: wohl run: cargo clippy --workspace --all-targets -- -D warnings + cargo-deny: + # Supply-chain gate: bans, licenses, advisories, sources. + # Configuration lives in deny.toml at the repo root. Duplicate- + # versions and unmaintained advisories currently warn rather than + # fail (see deny.toml comment and #8 follow-ups). + name: cargo-deny (supply-chain) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + path: wohl + - uses: actions/checkout@v4 + with: + repository: pulseengine/relay + path: relay + ref: ${{ env.RELAY_REF }} + fetch-depth: 0 + # cargo-deny-action handles install + cache + invocation. v2 is + # the current stable major (matches the action's tag scheme). + # If we want SHA-pinning parity with the rest of this workflow, + # bump to a sha256 in a follow-up — for now @v2 keeps us on + # security patches in the v2 major automatically. + - uses: EmbarkStudios/cargo-deny-action@v2 + with: + manifest-path: wohl/Cargo.toml + command: check bans licenses advisories sources + test: name: cargo test (workspace, incl. proptest) runs-on: ubuntu-latest @@ -89,8 +116,25 @@ jobs: run: cargo test --workspace --all-features kani: - name: Kani BMC (all components) + # Matrix split (was a single serial bash loop): each component gets + # its own job so a Kani failure names the offending crate in the + # GitHub UI without log-diving, and the 7 proofs run in parallel. + # Crates are hardcoded — dynamic discovery would mean parsing + # Cargo.toml at job-graph time, which GH Actions can't do cleanly. + # When a new Kani-verified crate lands, add it here. + name: "Kani: ${{ matrix.crate }}" runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + crate: + - wohl-leak + - wohl-temp + - wohl-air + - wohl-door + - wohl-power + - wohl-alert + - wohl-ota steps: - uses: actions/checkout@v4 with: @@ -107,7 +151,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: workspaces: wohl - key: kani + key: kani-${{ matrix.crate }} - name: Cache Kani install uses: actions/cache@v4 with: @@ -115,6 +159,10 @@ jobs: ~/.cargo/bin/cargo-kani ~/.cargo/bin/kani ~/.kani + # Shared Kani install cache across matrix cells: the toolchain + # bits are crate-independent and large (~1 GB), so per-cell + # caching would waste bandwidth and time. Per-crate target/ + # incremental dirs are covered by Swatinem/rust-cache above. key: kani-${{ runner.os }}-v1 - name: Install Kani run: | @@ -122,14 +170,9 @@ jobs: cargo install --locked kani-verifier fi cargo kani setup - - name: Run Kani on all components + - name: Run Kani on ${{ matrix.crate }} working-directory: wohl - run: | - for c in wohl-leak wohl-temp wohl-air wohl-door wohl-power wohl-alert wohl-ota; do - echo "::group::Kani $c" - cargo kani -p "$c" - echo "::endgroup::" - done + run: cargo kani -p "${{ matrix.crate }}" fuzz-smoke: name: cargo-fuzz smoke (60s/target) diff --git a/.github/workflows/nightly-fuzz.yml b/.github/workflows/nightly-fuzz.yml new file mode 100644 index 0000000..e18c688 --- /dev/null +++ b/.github/workflows/nightly-fuzz.yml @@ -0,0 +1,71 @@ +name: Nightly Fuzz + +# 15 min/target deep-fuzz, run nightly. The PR gate (ci.yml::fuzz-smoke) +# only runs 60s/target — enough to catch obvious regressions on every +# PR; this workflow exists to actually exercise the corpora. +# +# On failure: GitHub Actions emits the default notification to repo +# watchers. No Slack/email wiring here on purpose (out of scope for #8). + +on: + schedule: + # 03:00 UTC daily. + - cron: "0 3 * * *" + workflow_dispatch: + +concurrency: + # If a new nightly fires while the previous run is still going, + # cancel the in-flight one — we'd rather see the latest tip than + # double-book a runner. + group: nightly-fuzz + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + # IMPORTANT: this RELAY_REF must be kept in lock-step with the + # RELAY_REF in `.github/workflows/ci.yml`. When you bump one, bump + # the other in the same PR — otherwise the nightly fuzzes a + # different sibling-relay than the PR gate, which defeats the + # purpose of pinning. + RELAY_REF: 178ffd479ad863c91ece4f580379a9207c36a530 + +jobs: + fuzz: + name: "fuzz: ${{ matrix.target }} (15min)" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Mirrors fuzz/fuzz_targets/. Add new targets here as they land. + target: + - fuzz_leak + - fuzz_temp + steps: + - uses: actions/checkout@v4 + with: + path: wohl + - uses: actions/checkout@v4 + with: + repository: pulseengine/relay + path: relay + ref: ${{ env.RELAY_REF }} + fetch-depth: 0 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + - uses: Swatinem/rust-cache@v2 + with: + workspaces: wohl + key: fuzz-nightly-${{ matrix.target }} + - name: Install cargo-fuzz + # NOT --locked: matches ci.yml::fuzz-smoke — cargo-fuzz 0.13.1's + # lockfile pins rustix 0.36.5 which uses unstable rustc_attrs + # that current nightly no longer accepts. Unlocked resolution + # picks a newer rustix that compiles cleanly. + run: cargo install cargo-fuzz + - name: Fuzz ${{ matrix.target }} (15 min) + working-directory: wohl + # 900s = 15 min/target. Two targets => ~30 min wall-clock when + # the matrix runs in parallel, which is the steady state. + run: cargo fuzz run ${{ matrix.target }} -- -max_total_time=900 diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..ccc52f9 --- /dev/null +++ b/deny.toml @@ -0,0 +1,80 @@ +# cargo-deny configuration for wohl. +# +# Scope: bans, licenses, advisories, sources. Tuned conservatively for +# the first PR in #8 — duplicate-versions and unmaintained advisories +# warn rather than fail so we get visibility without redding CI on day 1. +# Strict-dedup and tighter advisory gating can come in a follow-up +# (see issue #8 follow-ups). + +[graph] +# Check the whole workspace as cargo sees it. wohl runs only on Linux +# targets in CI (no_std components are platform-agnostic, but their +# transitive build-graph today resolves cleanly on x86_64-unknown-linux-gnu). +all-features = true + +[licenses] +# Confidence threshold for license-file detection (askalono). +confidence-threshold = 0.93 +# Policy allow-list. The first block is licenses currently present in +# the resolved graph (`cargo deny list`); the second block is forward- +# looking allowances we accept without further review. cargo-deny +# emits a `license-not-encountered` warning for unused entries — that +# is informational, not a failure. +allow = [ + # Currently present in the wohl graph. + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "Unicode-3.0", + "Unlicense", + # Pre-approved for future deps (no review needed if a new + # transitive shows up under one of these). + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "MPL-2.0", + "Unicode-DFS-2016", + "CC0-1.0", + "Zlib", +] + +[bans] +# Wildcard deps: the wohl workspace itself has `path = "..."` deps to +# the sibling relay crates, which cargo-deny categorizes alongside +# wildcards in the "spec" sense. Allow for now. +wildcards = "allow" +# Duplicate versions: warn so we see drift, but don't fail the gate +# on day one. Strict-dedup is a follow-up. +multiple-versions = "warn" +# Don't ban any specific crate yet. +deny = [] +# No skips needed yet — if multiple-versions starts being noisy, add +# `skip = [...]` here with a justification. +skip = [] + +[advisories] +# RUSTSEC IDs we explicitly accept, each with a load-bearing reason. +# +# RUSTSEC-2026-0110 — `bare-metal` deprecated. Transitive via +# wohl-fw-door-bench → stm32g0xx-hal 0.2.0 → cortex-m 0.7.x → +# bare-metal {0.2.5, 1.0.0}. The advisory's own "Solution" reads +# "No safe upgrade is available!" — the upstream migration target +# (`critical-section`) is not yet reflected in the cortex-m 0.7.x +# release line that the entire embedded Rust ecosystem still pins. +# Re-evaluate when cortex-m 0.8 stabilises and stm32g0xx-hal cuts a +# release against it. +ignore = [ + "RUSTSEC-2026-0110", +] +# Yanked crates in the lockfile: warn, don't fail — yanks can happen +# mid-CI run. +yanked = "warn" + +[sources] +# Only crates.io and the wohl/relay/rivet git repos. The path-deps +# in this workspace resolve to local sibling checkouts, which cargo-deny +# treats as path sources (always allowed). +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = []