From 8e82595d68c9da5e410de09993b5b35740cd1a8e Mon Sep 17 00:00:00 2001 From: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> Date: Tue, 16 Jun 2026 11:56:08 +0200 Subject: [PATCH] ci: pin Rust to 1.96.0 via rust-toolchain.toml, drop MSRV Pins the toolchain so local and CI build with the identical compiler, instead of floating on whatever 'stable'/'nightly' is current that day (which, with clippy -D warnings, lets a new release break unrelated PRs). - rust-toolchain.toml: channel 1.96.0 + clippy/rustfmt components (stable pin) - .rust-nightly: the pinned nightly, single source of truth referenced by both the Makefile (fuzz-check) and the fuzz CI jobs, which select it explicitly with `cargo +$(cat .rust-nightly)` since the root stable pin would otherwise win in the fuzz workspace - Cargo.toml: remove rust-version (MSRV) from the workspace and all members - workflows: dtolnay/rust-toolchain -> actions-rust-lang/setup-rust-toolchain, which reads rust-toolchain.toml. Stable jobs install from the file; fuzz jobs install the pinned nightly from .rust-nightly; docs stays on floating nightly (cargo-doc-md selects nightly by alias internally) - fold Swatinem/rust-cache into the action's built-in cache (cache-workspaces for fuzz, cache-shared-key for docs); fmt and release stay cache-less - rustflags: "" on most jobs to keep RUSTFLAGS unset; the check job keeps the action default (-D warnings) so it fails the build/tests on any warning - fix clippy::duration_suboptimal_units surfaced by 1.96.0 (from_secs -> from_mins across firma-stack, firma-proto, firma-sidecar) - proxy_bridge: move the Unix-only HostBridgeHandle tests into a #[cfg(unix)] submodule so their imports don't trip unused-import on Windows now that the check job denies warnings in test builds --- .github/workflows/bench.yml | 6 ++-- .github/workflows/ci.yml | 31 ++++++++++--------- .github/workflows/demo-e2e.yml | 6 ++-- .github/workflows/docs.yml | 15 +++++---- .github/workflows/fuzz.yml | 14 ++++++--- .github/workflows/release.yml | 6 ++-- .rust-nightly | 1 + Cargo.toml | 1 - Makefile | 4 +-- crates/firma-authority/Cargo.toml | 1 - crates/firma-config/Cargo.toml | 1 - crates/firma-core/Cargo.toml | 1 - crates/firma-demo-fixture/Cargo.toml | 1 - crates/firma-demo-tui/Cargo.toml | 1 - .../firma-grpc-interceptor-proto/Cargo.toml | 1 - crates/firma-proto/Cargo.toml | 1 - crates/firma-proto/src/client.rs | 2 +- crates/firma-run/Cargo.toml | 1 - crates/firma-run/src/proxy_bridge.rs | 22 ++++++++----- crates/firma-sidecar/Cargo.toml | 1 - .../firma-sidecar/src/local_exec/endpoint.rs | 4 +-- .../firma-sidecar/src/local_exec/handler.rs | 2 +- .../src/local_exec/token_store.rs | 4 +-- crates/firma-stack/Cargo.toml | 1 - crates/firma-stack/src/start.rs | 6 ++-- crates/firma/Cargo.toml | 1 - rust-toolchain.toml | 3 ++ 27 files changed, 74 insertions(+), 64 deletions(-) create mode 100644 .rust-nightly create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 75cd5689..2334c3ff 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -18,9 +18,9 @@ jobs: with: persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - - uses: Swatinem/rust-cache@v2 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + rustflags: "" - name: Install protoc uses: arduino/setup-protoc@v3 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a834339..27bdcae8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,13 +67,13 @@ jobs: - uses: actions/checkout@v6 with: persist-credentials: false - # dprint formats TOML + Markdown + Rust. The exec plugin shells out to - # rustfmt for Rust, so the toolchain (with rustfmt) must be on PATH first. - # Plugin versions/checksums are pinned in dprint.json, so output is - # deterministic regardless of CLI version. - - uses: dtolnay/rust-toolchain@stable + # dprint formats TOML + Markdown + Rust via its exec plugin, which shells + # out to rustfmt, so the toolchain must be present. setup-rust-toolchain + # installs it from rust-toolchain.toml (including the rustfmt component). + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: - components: rustfmt + cache: false + rustflags: "" - uses: dprint/check@v2.3 check: @@ -88,11 +88,7 @@ jobs: with: persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - - uses: Swatinem/rust-cache@v2 + - uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Install protoc uses: arduino/setup-protoc@v3 @@ -116,11 +112,14 @@ jobs: with: persist-credentials: false - - uses: dtolnay/rust-toolchain@nightly + - id: nightly + run: echo "toolchain=$(cat .rust-nightly)" >> "$GITHUB_OUTPUT" - - uses: Swatinem/rust-cache@v2 + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: - workspaces: fuzz -> fuzz/target + toolchain: ${{ steps.nightly.outputs.toolchain }} + rustflags: "" + cache-workspaces: fuzz -> fuzz/target - name: Install protoc uses: arduino/setup-protoc@v3 @@ -128,5 +127,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Check fuzz targets - run: cargo check + env: + NIGHTLY: ${{ steps.nightly.outputs.toolchain }} + run: cargo "+$NIGHTLY" check working-directory: fuzz diff --git a/.github/workflows/demo-e2e.yml b/.github/workflows/demo-e2e.yml index 4c8b6118..e5b89da6 100644 --- a/.github/workflows/demo-e2e.yml +++ b/.github/workflows/demo-e2e.yml @@ -21,9 +21,9 @@ jobs: with: persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - - uses: Swatinem/rust-cache@v2 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + rustflags: "" - name: Install protoc uses: arduino/setup-protoc@v3 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index eda7ed2b..72aa02bc 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -35,14 +35,17 @@ jobs: persist-credentials: false - name: Install Rust nightly (required by cargo-doc-md) - uses: dtolnay/rust-toolchain@nightly - - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly + cache: false + rustflags: "" - - uses: Swatinem/rust-cache@v2 + - name: Install Rust (pinned in rust-toolchain.toml) + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - shared-key: docs + rustflags: "" + cache-shared-key: docs - name: Install protoc uses: arduino/setup-protoc@v3 diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 5872704a..0c8821c7 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -22,11 +22,14 @@ jobs: with: persist-credentials: false - - uses: dtolnay/rust-toolchain@nightly + - id: nightly + run: echo "toolchain=$(cat .rust-nightly)" >> "$GITHUB_OUTPUT" - - uses: Swatinem/rust-cache@v2 + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: - workspaces: fuzz -> fuzz/target + toolchain: ${{ steps.nightly.outputs.toolchain }} + rustflags: "" + cache-workspaces: fuzz -> fuzz/target - name: Install protoc uses: arduino/setup-protoc@v3 @@ -40,4 +43,7 @@ jobs: run: cargo binstall --no-confirm --locked cargo-fuzz - name: Run ${{ matrix.target }} - run: cargo fuzz run --target x86_64-unknown-linux-gnu ${{ matrix.target }} -- -max_total_time=300 + env: + NIGHTLY: ${{ steps.nightly.outputs.toolchain }} + TARGET: ${{ matrix.target }} + run: cargo "+$NIGHTLY" fuzz run --target x86_64-unknown-linux-gnu "$TARGET" -- -max_total_time=300 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 22111a77..a2619f94 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,9 +64,11 @@ jobs: fi echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" - - uses: dtolnay/rust-toolchain@stable + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: - targets: ${{ matrix.target }} + target: ${{ matrix.target }} + cache: false + rustflags: "" - name: Install protoc uses: arduino/setup-protoc@v3 diff --git a/.rust-nightly b/.rust-nightly new file mode 100644 index 00000000..f692697d --- /dev/null +++ b/.rust-nightly @@ -0,0 +1 @@ +nightly-2026-06-04 diff --git a/Cargo.toml b/Cargo.toml index 338cae8f..b4a28458 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ version = "0.1.1" edition = "2024" license = "Apache-2.0" repository = "https://github.com/Firma-AI/openfirma" -rust-version = "1.88.0" [workspace.lints.clippy] pedantic = { level = "deny", priority = -1 } diff --git a/Makefile b/Makefile index 6fa946fe..247e64de 100644 --- a/Makefile +++ b/Makefile @@ -60,9 +60,9 @@ deny: check: fmt lint test build audit deny -# Requires nightly: rustup toolchain install nightly +# Pinned nightly lives in .rust-nightly (cargo-fuzz requires nightly). fuzz-check: - cd fuzz && cargo +nightly check + cd fuzz && cargo +$(shell cat .rust-nightly) check bench: cargo bench --workspace --no-fail-fast diff --git a/crates/firma-authority/Cargo.toml b/crates/firma-authority/Cargo.toml index ffa5d86f..644e58e2 100644 --- a/crates/firma-authority/Cargo.toml +++ b/crates/firma-authority/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Mini Authority — policy loading, capability issuance, and gRPC streams for Firma OSS" [dependencies] diff --git a/crates/firma-config/Cargo.toml b/crates/firma-config/Cargo.toml index 00692e70..9e8376a6 100644 --- a/crates/firma-config/Cargo.toml +++ b/crates/firma-config/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Platform config-path discovery for the firma CLI" [lints] diff --git a/crates/firma-core/Cargo.toml b/crates/firma-core/Cargo.toml index c0b564b4..557d05c4 100644 --- a/crates/firma-core/Cargo.toml +++ b/crates/firma-core/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Shared types, capability tokens, Cedar wrapper, and error types for Firma OSS" [dependencies] diff --git a/crates/firma-demo-fixture/Cargo.toml b/crates/firma-demo-fixture/Cargo.toml index fff7ba8e..578ad0fe 100644 --- a/crates/firma-demo-fixture/Cargo.toml +++ b/crates/firma-demo-fixture/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Deterministic ALLOW/DENY fixture server and CI driver client for the Firma demo" [lints] diff --git a/crates/firma-demo-tui/Cargo.toml b/crates/firma-demo-tui/Cargo.toml index 3dc91e22..2e03de2d 100644 --- a/crates/firma-demo-tui/Cargo.toml +++ b/crates/firma-demo-tui/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true [[bin]] name = "firma-demo-tui" diff --git a/crates/firma-grpc-interceptor-proto/Cargo.toml b/crates/firma-grpc-interceptor-proto/Cargo.toml index 8bc6f5fc..c8af9cff 100644 --- a/crates/firma-grpc-interceptor-proto/Cargo.toml +++ b/crates/firma-grpc-interceptor-proto/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Protobuf/gRPC definitions for the Firma interceptor hook (agent to sidecar)" [dependencies] diff --git a/crates/firma-proto/Cargo.toml b/crates/firma-proto/Cargo.toml index 22b0523a..53cafa94 100644 --- a/crates/firma-proto/Cargo.toml +++ b/crates/firma-proto/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Protobuf/gRPC service definitions and generated code for Firma OSS" [dependencies] diff --git a/crates/firma-proto/src/client.rs b/crates/firma-proto/src/client.rs index 31f050af..7bb2ba26 100644 --- a/crates/firma-proto/src/client.rs +++ b/crates/firma-proto/src/client.rs @@ -59,7 +59,7 @@ pub fn build_channel( .connect_timeout(connect_timeout) .keep_alive_timeout(Duration::from_secs(30)) .http2_keep_alive_interval(Duration::from_secs(30)) - .tcp_keepalive(Some(Duration::from_secs(60))); + .tcp_keepalive(Some(Duration::from_mins(1))); match scheme.as_str() { "https" => { diff --git a/crates/firma-run/Cargo.toml b/crates/firma-run/Cargo.toml index b217ac86..8fa926f3 100644 --- a/crates/firma-run/Cargo.toml +++ b/crates/firma-run/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true [lints] workspace = true diff --git a/crates/firma-run/src/proxy_bridge.rs b/crates/firma-run/src/proxy_bridge.rs index 46bf87cc..0888d707 100644 --- a/crates/firma-run/src/proxy_bridge.rs +++ b/crates/firma-run/src/proxy_bridge.rs @@ -810,12 +810,7 @@ fn find_crlf(buffer: &[u8]) -> Option { #[cfg(test)] mod tests { use std::collections::BTreeMap; - use std::io::{Read, Write}; - use std::net::{SocketAddr, TcpListener, TcpStream}; - use std::time::Duration; - #[cfg(unix)] - use super::HostBridgeHandle; use super::{BodyKind, append_missing_headers, find_header_terminator, parse_request_metadata}; #[test] @@ -874,6 +869,20 @@ mod tests { let meta = parse_request_metadata(req).expect("metadata"); assert_eq!(meta.body, BodyKind::Chunked); } +} + +// The bridge spins up real TCP listeners and only exists on the non-structural +// (macOS) path, so these tests are Unix-only and grouped here to keep the +// platform gate on the module rather than on each test and import. +#[cfg(test)] +#[cfg(unix)] +mod host_bridge_tests { + use std::collections::BTreeMap; + use std::io::{Read, Write}; + use std::net::{SocketAddr, TcpListener, TcpStream}; + use std::time::Duration; + + use super::HostBridgeHandle; /// Verifies that `HostBridgeHandle` injects `x-firma-session-id` into a /// plain HTTP request routed through the bridge. @@ -881,7 +890,6 @@ mod tests { /// This is the regression test for FIR-213: on macOS (non-structural path) /// the bridge was never started, so the sidecar received an empty /// `session_id` and denied every request. - #[cfg(unix)] #[test] fn host_bridge_injects_session_id_into_http_request() { // ── upstream mock ────────────────────────────────────────────────── @@ -951,7 +959,6 @@ mod tests { /// Verifies that `HostBridgeHandle` injects `x-firma-session-id` into the /// CONNECT request that Claude Code issues for HTTPS destinations. - #[cfg(unix)] #[test] fn host_bridge_injects_session_id_into_connect_request() { let upstream_listener = TcpListener::bind("127.0.0.1:0").expect("upstream bind"); @@ -1013,7 +1020,6 @@ mod tests { /// Verifies that an existing `x-firma-session-id` header is not /// duplicated when the client already carries one. - #[cfg(unix)] #[test] fn host_bridge_does_not_duplicate_existing_session_id() { let upstream_listener = TcpListener::bind("127.0.0.1:0").expect("upstream bind"); diff --git a/crates/firma-sidecar/Cargo.toml b/crates/firma-sidecar/Cargo.toml index 1eae7870..695b18b1 100644 --- a/crates/firma-sidecar/Cargo.toml +++ b/crates/firma-sidecar/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "HTTP proxy sidecar with enforcement pipeline for Firma OSS" [features] diff --git a/crates/firma-sidecar/src/local_exec/endpoint.rs b/crates/firma-sidecar/src/local_exec/endpoint.rs index 3daa4409..b600fd28 100644 --- a/crates/firma-sidecar/src/local_exec/endpoint.rs +++ b/crates/firma-sidecar/src/local_exec/endpoint.rs @@ -135,7 +135,7 @@ impl LocalExecEndpoint { let store_for_pruner = self.handler.token_store(); let prune_cancel = cancel.clone(); tokio::spawn(async move { - let mut interval = tokio::time::interval(Duration::from_secs(60)); + let mut interval = tokio::time::interval(Duration::from_mins(1)); loop { tokio::select! { () = prune_cancel.cancelled() => break, @@ -375,7 +375,7 @@ mod tests { let socket_path = tmp.path().join("test-local-exec.sock"); let config = LocalExecHandlerConfig { default_action: action, - token_ttl: Duration::from_secs(60), + token_ttl: Duration::from_mins(1), retry_after_ms: 500, }; let handler = LocalExecHandler::new(config); diff --git a/crates/firma-sidecar/src/local_exec/handler.rs b/crates/firma-sidecar/src/local_exec/handler.rs index 67242660..630caceb 100644 --- a/crates/firma-sidecar/src/local_exec/handler.rs +++ b/crates/firma-sidecar/src/local_exec/handler.rs @@ -466,7 +466,7 @@ mod tests { fn config(action: DefaultAction) -> LocalExecHandlerConfig { LocalExecHandlerConfig { default_action: action, - token_ttl: Duration::from_secs(60), + token_ttl: Duration::from_mins(1), retry_after_ms: 500, } } diff --git a/crates/firma-sidecar/src/local_exec/token_store.rs b/crates/firma-sidecar/src/local_exec/token_store.rs index e91dc26f..78e2c7b9 100644 --- a/crates/firma-sidecar/src/local_exec/token_store.rs +++ b/crates/firma-sidecar/src/local_exec/token_store.rs @@ -218,7 +218,7 @@ impl InMemoryTokenStore { Self { tokens: Mutex::new(HashMap::new()), ttl, - expiry_grace: Duration::from_secs(300), + expiry_grace: Duration::from_mins(5), } } @@ -423,7 +423,7 @@ mod tests { use super::*; fn store() -> InMemoryTokenStore { - InMemoryTokenStore::new(Duration::from_secs(60)) + InMemoryTokenStore::new(Duration::from_mins(1)) } fn issue_token(store: &InMemoryTokenStore) -> String { diff --git a/crates/firma-stack/Cargo.toml b/crates/firma-stack/Cargo.toml index 6837bc9c..22ebd558 100644 --- a/crates/firma-stack/Cargo.toml +++ b/crates/firma-stack/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Process supervision and observability primitives for the firma stack" [lints] diff --git a/crates/firma-stack/src/start.rs b/crates/firma-stack/src/start.rs index d176e322..d4e54a3e 100644 --- a/crates/firma-stack/src/start.rs +++ b/crates/firma-stack/src/start.rs @@ -91,7 +91,7 @@ fn spawn_stack_inner(cfg: &StackConfig, state_dir: &Path) -> Result let auth_addr = read_authority_listen_addr(&cfg.config_file)?; std::fs::write(state_dir.join("authority.listen"), format!("{auth_addr}\n"))?; debug!(addr = %auth_addr, "waiting for authority TCP listen"); - wait_for_tcp("authority", auth_addr, Duration::from_secs(60))?; + wait_for_tcp("authority", auth_addr, Duration::from_mins(1))?; info!(addr = %auth_addr, "authority listening"); debug!(config = %cfg.config_file.display(), exe = ?exe, "spawning sidecar"); @@ -100,12 +100,12 @@ fn spawn_stack_inner(cfg: &StackConfig, state_dir: &Path) -> Result let side_addr = read_sidecar_listen_addr(&cfg.config_file)?; std::fs::write(state_dir.join("sidecar.listen"), format!("{side_addr}\n"))?; debug!(addr = %side_addr, "waiting for sidecar TCP listen"); - wait_for_tcp("sidecar", side_addr, Duration::from_secs(60))?; + wait_for_tcp("sidecar", side_addr, Duration::from_mins(1))?; info!(addr = %side_addr, "sidecar listening"); debug!("waiting for sidecar CA material"); wait_for_ca_material( &state_dir.join("generated-firma-ca"), - Duration::from_secs(60), + Duration::from_mins(1), )?; debug!("CA material present"); diff --git a/crates/firma/Cargo.toml b/crates/firma/Cargo.toml index d156383b..9b370675 100644 --- a/crates/firma/Cargo.toml +++ b/crates/firma/Cargo.toml @@ -4,7 +4,6 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -rust-version.workspace = true description = "Firma CLI for managing and running OpenFirma components" [[bin]] diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..01a3bee0 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.96.0" +components = ["clippy", "rustfmt"]