diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fe5de6c..943afa5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,6 +56,8 @@ jobs: # job. - name: Zero leak run: ./scripts/zero-leak.sh + - name: MSRV + run: ./scripts/msrv-lint.sh fmt: name: rustfmt diff --git a/AGENTS.md b/AGENTS.md index e99ce75e..ab50ec2e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,8 @@ The videre and shepherd repositories build on the SDK and the runtime published ## Build, test, lint The workspace uses Rust edition 2024. -The flake pins the toolchain to Rust 1.94.0, which matches the toolchain CI installs. +The flake pins the toolchain to Rust 1.94.0, which matches the toolchain CI installs and the `rust-version` every crate inherits. +`just msrv` proves the three have not drifted, and holds the licence to one inherited SPDX identifier. Run `nix develop` to enter the dev shell, or run `direnv allow` once. The dev shell supplies the toolchain, the `wasm32-wasip2` target, `cargo-nextest`, `just`, `ripgrep`, and `ast-grep`. diff --git a/Cargo.toml b/Cargo.toml index 47fa7f3b..f813ed99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,12 +21,14 @@ members = [ "modules/fixtures/slow-host", "modules/fixtures/topic-parity", ] -resolver = "2" +# A virtual manifest has no edition, so an absent key means resolver 1. +resolver = "3" [workspace.package] version = "0.1.0" edition = "2024" -license = "AGPL-3.0" +license = "AGPL-3.0-or-later" +rust-version = "1.94.0" repository = "https://github.com/nullislabs/nexum-runtime" # Shared dependency table. Every external dependency is hoisted here. @@ -217,6 +219,7 @@ todo = "deny" unwrap_used = "deny" expect_used = "warn" +# Cargo forces unwind on test and bench regardless, so `#[should_panic]` works. [profile.dev] panic = "abort" diff --git a/README.md b/README.md index f0d2a1b3..614dd85e 100644 --- a/README.md +++ b/README.md @@ -70,4 +70,4 @@ Against a compromised artifact store, supply an operator-owned manifest from out ## Licence -AGPL-3.0. See [LICENSE](LICENSE). +AGPL-3.0-or-later. See [LICENSE](LICENSE). diff --git a/crates/nexum-cli/Cargo.toml b/crates/nexum-cli/Cargo.toml index fa2c3d77..8c4edd45 100644 --- a/crates/nexum-cli/Cargo.toml +++ b/crates/nexum-cli/Cargo.toml @@ -2,6 +2,7 @@ name = "nexum-cli" version.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true diff --git a/crates/nexum-launch/Cargo.toml b/crates/nexum-launch/Cargo.toml index 2797184f..45cc65dd 100644 --- a/crates/nexum-launch/Cargo.toml +++ b/crates/nexum-launch/Cargo.toml @@ -2,6 +2,7 @@ name = "nexum-launch" version.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true diff --git a/crates/nexum-module-macros/Cargo.toml b/crates/nexum-module-macros/Cargo.toml index ea5a020f..bb5df461 100644 --- a/crates/nexum-module-macros/Cargo.toml +++ b/crates/nexum-module-macros/Cargo.toml @@ -2,6 +2,7 @@ name = "nexum-module-macros" version.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Proc-macro glue for nexum runtime modules: #[module] emits the per-cdylib wit-bindgen, host adapter, event dispatch, and export." diff --git a/crates/nexum-runtime/Cargo.toml b/crates/nexum-runtime/Cargo.toml index 700092e7..9ffdfcf6 100644 --- a/crates/nexum-runtime/Cargo.toml +++ b/crates/nexum-runtime/Cargo.toml @@ -2,6 +2,7 @@ name = "nexum-runtime" version.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true diff --git a/crates/nexum-sdk-test/Cargo.toml b/crates/nexum-sdk-test/Cargo.toml index 7a9ca9b7..9c72a1f9 100644 --- a/crates/nexum-sdk-test/Cargo.toml +++ b/crates/nexum-sdk-test/Cargo.toml @@ -2,6 +2,7 @@ name = "nexum-sdk-test" version.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "In-memory host mocks for nexum module unit tests. Implements nexum_sdk::host::{ChainHost, LocalStoreHost, LoggingHost}." diff --git a/crates/nexum-sdk/Cargo.toml b/crates/nexum-sdk/Cargo.toml index f04525d3..8008e612 100644 --- a/crates/nexum-sdk/Cargo.toml +++ b/crates/nexum-sdk/Cargo.toml @@ -2,6 +2,7 @@ name = "nexum-sdk" version.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Guest-side SDK for nexum runtime modules: host-neutral helpers usable by any module, independent of the CoW domain layer." diff --git a/crates/nexum-tasks/Cargo.toml b/crates/nexum-tasks/Cargo.toml index 1bd9d87e..473e0120 100644 --- a/crates/nexum-tasks/Cargo.toml +++ b/crates/nexum-tasks/Cargo.toml @@ -2,6 +2,7 @@ name = "nexum-tasks" version.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Task lifecycle and graceful shutdown for the Nexum runtime" diff --git a/crates/nexum-world/Cargo.toml b/crates/nexum-world/Cargo.toml index dbc149a1..6c40f996 100644 --- a/crates/nexum-world/Cargo.toml +++ b/crates/nexum-world/Cargo.toml @@ -2,6 +2,7 @@ name = "nexum-world" version.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Per-module WIT world synthesis: the core capability table, registry-driven extension rows, manifest parsing, and crate-local WIT package resolution." diff --git a/flake.nix b/flake.nix index 788622e7..c8381a4a 100644 --- a/flake.nix +++ b/flake.nix @@ -18,7 +18,8 @@ inherit (pkgs) lib stdenv; - # Pinned to match CI exactly (.github/workflows/ci.yml uses "1.94"). + # scripts/msrv-lint.sh holds this, Cargo.toml and the rust-setup + # action on one version. # Develop with `nix develop` so the local toolchain matches CI/CD. rustToolchain = pkgs.rust-bin.stable."1.94.0".default.override { extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ]; diff --git a/justfile b/justfile index 0fe342e4..4d7ccb24 100644 --- a/justfile +++ b/justfile @@ -52,6 +52,9 @@ content range="main..HEAD": zero-leak: ./scripts/zero-leak.sh +msrv: + ./scripts/msrv-lint.sh + # Check the workspace quickly. check: cargo check --target wasm32-wasip2 -p example @@ -59,10 +62,10 @@ check: cargo check -p nexum-cli # Run the full CI series locally before pushing. Mirrors -# .github/workflows/ci.yml one-to-one: house style, the zero-leak gate, -# rustfmt, clippy, rustdoc, the module wasms the integration tests need, and -# the workspace test suite via nextest plus the doctests, all under the -# `-D warnings` the CI workflow sets globally. +# .github/workflows/ci.yml one-to-one: house style, the zero-leak gate, the +# MSRV gate, rustfmt, clippy, rustdoc, the module wasms the integration tests +# need, and the workspace test suite via nextest plus the doctests, all under +# the `-D warnings` the CI workflow sets globally. ci: #!/usr/bin/env bash set -euo pipefail @@ -73,6 +76,7 @@ ci: export RUSTDOCFLAGS="${RUSTDOCFLAGS:-} -D warnings" ./scripts/content-lint.sh "main..HEAD" ./scripts/zero-leak.sh + ./scripts/msrv-lint.sh cargo fmt --all --check cargo clippy --workspace --all-targets --all-features -- -D warnings cargo doc --workspace --all-features --no-deps diff --git a/modules/example/Cargo.toml b/modules/example/Cargo.toml index 19311814..9e2e3977 100644 --- a/modules/example/Cargo.toml +++ b/modules/example/Cargo.toml @@ -2,6 +2,7 @@ name = "example" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true diff --git a/modules/examples/balance-tracker/Cargo.toml b/modules/examples/balance-tracker/Cargo.toml index a6001d34..a89cd018 100644 --- a/modules/examples/balance-tracker/Cargo.toml +++ b/modules/examples/balance-tracker/Cargo.toml @@ -2,6 +2,7 @@ name = "balance-tracker" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Example module: tracks native-token balances of a list of addresses and emits a log when one changes by more than a threshold. Demonstrates chain::request + local-store + multi-key persistence." diff --git a/modules/examples/http-probe/Cargo.toml b/modules/examples/http-probe/Cargo.toml index 46910bd3..89cf6163 100644 --- a/modules/examples/http-probe/Cargo.toml +++ b/modules/examples/http-probe/Cargo.toml @@ -2,6 +2,7 @@ name = "http-probe" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Example module: fetches an allowlisted URL over wasi:http on every block and verifies the off-list path is denied." diff --git a/modules/examples/price-alert/Cargo.toml b/modules/examples/price-alert/Cargo.toml index 1dc3395b..935ff9dd 100644 --- a/modules/examples/price-alert/Cargo.toml +++ b/modules/examples/price-alert/Cargo.toml @@ -2,6 +2,7 @@ name = "price-alert" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Example module: polls a Chainlink price oracle every block and emits a Warn log when the price crosses a config-supplied threshold." diff --git a/modules/fixtures/clock-reader/Cargo.toml b/modules/fixtures/clock-reader/Cargo.toml index 6359fcc6..5a2be467 100644 --- a/modules/fixtures/clock-reader/Cargo.toml +++ b/modules/fixtures/clock-reader/Cargo.toml @@ -2,6 +2,7 @@ name = "clock-reader" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Test fixture: on every event reads the WASI wall clock through std and logs it. Lets a test assert the guest observes a WasiClockOverride end to end." diff --git a/modules/fixtures/env-reader/Cargo.toml b/modules/fixtures/env-reader/Cargo.toml index cf4a3ae5..83b398c5 100644 --- a/modules/fixtures/env-reader/Cargo.toml +++ b/modules/fixtures/env-reader/Cargo.toml @@ -2,6 +2,7 @@ name = "env-reader" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Test fixture: on every event counts the guest-visible environment variables, process arguments, and stdin bytes through std and logs all three. Lets a test assert the guest observes an empty wasi:cli/environment and an empty wasi:cli/stdin end to end." diff --git a/modules/fixtures/flaky-bomb/Cargo.toml b/modules/fixtures/flaky-bomb/Cargo.toml index f040e846..66122923 100644 --- a/modules/fixtures/flaky-bomb/Cargo.toml +++ b/modules/fixtures/flaky-bomb/Cargo.toml @@ -2,6 +2,7 @@ name = "flaky-bomb" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Evil-by-design fixture: traps on the first N events (via unreachable!) and succeeds afterwards. The supervisor must exercise its exponential-backoff restart policy + reset the failure counter when the module recovers." diff --git a/modules/fixtures/fuel-bomb/Cargo.toml b/modules/fixtures/fuel-bomb/Cargo.toml index 9692cc42..e01498bc 100644 --- a/modules/fixtures/fuel-bomb/Cargo.toml +++ b/modules/fixtures/fuel-bomb/Cargo.toml @@ -2,6 +2,7 @@ name = "fuel-bomb" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Evil-by-design fixture: on every event runs an unbounded loop to exhaust the wasmtime fuel budget. Engine must trap with OutOfFuel + mark the module dead." diff --git a/modules/fixtures/memory-bomb/Cargo.toml b/modules/fixtures/memory-bomb/Cargo.toml index 09c352fc..83876207 100644 --- a/modules/fixtures/memory-bomb/Cargo.toml +++ b/modules/fixtures/memory-bomb/Cargo.toml @@ -2,6 +2,7 @@ name = "memory-bomb" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Evil-by-design fixture: on every event allocates past the 64 MiB memory cap to force a memory-growth trap. Engine must trap + mark the module dead without taking down the supervisor." diff --git a/modules/fixtures/panic-bomb/Cargo.toml b/modules/fixtures/panic-bomb/Cargo.toml index af834858..1752971d 100644 --- a/modules/fixtures/panic-bomb/Cargo.toml +++ b/modules/fixtures/panic-bomb/Cargo.toml @@ -2,6 +2,7 @@ name = "panic-bomb" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Evil-by-design fixture: installs the nexum-sdk tracing facade in init and panics on every event. One death must leave Stderr, HostInterface, and Panic records on the run." diff --git a/modules/fixtures/slow-host/Cargo.toml b/modules/fixtures/slow-host/Cargo.toml index 8206cab4..706b1961 100644 --- a/modules/fixtures/slow-host/Cargo.toml +++ b/modules/fixtures/slow-host/Cargo.toml @@ -2,6 +2,7 @@ name = "slow-host" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true description = "Evil-by-design fixture: on_event issues a single chain::request host call that the test wires to a mock provider which parks the request far past the dispatch deadline. Proves the supervisor cuts off a blocked host call and recovers on a fresh store, which fuel and epoch metering cannot do." diff --git a/modules/fixtures/topic-parity/Cargo.toml b/modules/fixtures/topic-parity/Cargo.toml index 292078a7..2c786e03 100644 --- a/modules/fixtures/topic-parity/Cargo.toml +++ b/modules/fixtures/topic-parity/Cargo.toml @@ -2,6 +2,7 @@ name = "topic-parity" version = "0.1.0" edition.workspace = true +rust-version.workspace = true license.workspace = true repository.workspace = true diff --git a/scripts/msrv-lint.sh b/scripts/msrv-lint.sh new file mode 100755 index 00000000..9f946072 --- /dev/null +++ b/scripts/msrv-lint.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# Three files state the toolchain version independently and none of them is +# compiled, so any one can drift without an error: Cargo.toml, the flake pin, +# and the CI action input. The action names two components, which rustup +# resolves to the newest patch, so it is compared on major.minor. +# +# The licence rides along because cargo performs no SPDX validation at all: +# `cargo publish --dry-run` packages and compiles a member licensed +# `NOT-A-REAL-LICENCE` and exits 0. + +set -euo pipefail + +root=$(git rev-parse --show-toplevel) +cd "$root" + +status=0 +fail() { + printf ' %s\n' "$1" >&2 + status=1 +} + +MANIFEST=Cargo.toml +FLAKE=flake.nix +ACTION=.github/actions/rust-setup/action.yml + +# `|| true`: a missing key must reach the report, not die on grep's exit status. +msrv=$(grep -oE '^rust-version = "[0-9.]+"' "$MANIFEST" | sed -e 's/.*"\(.*\)"/\1/' || true) +flake=$(grep -oE 'rust-bin\.stable\."[0-9.]+"' "$FLAKE" | sed -e 's/.*"\(.*\)"/\1/' || true) +action=$(grep -oE 'toolchain: "[0-9.]+"' "$ACTION" | sed -e 's/.*"\(.*\)"/\1/' || true) + +echo "msrv-lint: $MANIFEST $msrv, $FLAKE $flake, $ACTION $action" + +for pair in "$MANIFEST:$msrv" "$FLAKE:$flake" "$ACTION:$action"; do + if [ -z "${pair#*:}" ]; then + fail "${pair%%:*} states no Rust version, or states it in a form this check cannot read" + fi +done + +if [ "$status" -eq 0 ]; then + if [ "$msrv" != "$flake" ]; then + fail "$MANIFEST rust-version $msrv does not match the $FLAKE pin $flake" + fi + if [ "$(cut -d. -f1,2 <<<"$action")" != "$(cut -d. -f1,2 <<<"$flake")" ]; then + fail "$ACTION toolchain $action does not match the $FLAKE pin $flake" + fi +fi + +# Matched by shape, not against this workspace's choice, so a licence change +# needs no edit here. +license=$(grep -oE '^license = "[^"]+"' "$MANIFEST" | sed -e 's/.*"\(.*\)"/\1/' || true) +echo "msrv-lint: $MANIFEST licence $license" +if [ -z "$license" ]; then + fail "$MANIFEST states no license on [workspace.package]" +elif [[ "$license" =~ ^(AGPL|LGPL|GPL)-[0-9]+\.[0-9]+$ ]]; then + fail "$MANIFEST license $license is deprecated in the SPDX list; write $license-only or $license-or-later" +fi + +echo "msrv-lint: every member inherits rust-version and license" +while read -r member; do + [ -n "$member" ] || continue + [ "$member" = "$MANIFEST" ] && continue + for key in rust-version license; do + if ! grep -qE "^$key\.workspace = true\$" "$member"; then + fail "$member does not carry $key.workspace = true" + fi + done +done < <(git ls-files '*Cargo.toml') + +if [ "$status" -eq 0 ]; then + echo "msrv-lint: ok" +fi +exit "$status"