Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 52 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -107,29 +151,28 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
workspaces: wohl
key: kani
key: kani-${{ matrix.crate }}
- name: Cache Kani install
uses: actions/cache@v4
with:
path: |
~/.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: |
if ! command -v cargo-kani >/dev/null 2>&1; then
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)
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/nightly-fuzz.yml
Original file line number Diff line number Diff line change
@@ -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
80 changes: 80 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
@@ -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 = []
Loading