Repository navigation
ci: per-crate feature matrix check (cargo hack --each-feature) to close the workspace-unification blind spot #240
Description
Problem
CI's feature coverage has a structural blind spot: both existing checks run workspace-scoped, so cargo unifies features across all members and every crate's feature set is validated only in combination — never in the configurations a downstream consumer or a cargo publish would actually build.
$ cargo check --workspace --all-features # 0 errors on the commit that introduced #233
$ cargo check --workspace --no-default-features # 0 errors on the commit that introduced #236Two shipped bugs prove the class (writeups: #233, #236, and the verification matrix in #233 (comment)):
- bug: arc-consensus-types
arbitraryfeature does not build standalone #233 —arc-consensus-types/arbitrarydidn't build standalone; masked becausearc-consensus-dbandarc-node-consensusdev-depend on the feature and other members' flags supplied the missing deps under unification. Fixed in fix(types): make the arbitrary feature self-contained #231. - bug: arc-consensus-types does not build with --no-default-features;
signer-localgates a mandatory dependency #236 —arc-consensus-types --no-default-featuresdidn't build at all; thesigner-localfeature gated a dependency four modules import unconditionally. Fixed in fix(types): make malachitebft-signing-ed25519 a required dependency #237.
Both directions of the same defect: a feature that only builds because of workspace unification, and a crate that only builds because of its default features. Since crates/types inherits publish from the workspace, per-crate feature buildability is publishing correctness, not hygiene.
Proposed job
feature-matrix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable # pin to rust-toolchain.toml (1.91.1)
- uses: taiki-e/install-action@cargo-hack
- uses: Swatinem/rust-cache@v2
- run: cargo hack check --each-feature --workspace --exclude arc-signer
- run: cargo hack check --each-feature -p arc-signer --at-least-one-of local,remotecargo-hack decomposes --workspace into per-package runs, which is the entire mechanism — adding flags to the existing workspace-scoped jobs catches nothing (verified in the comment linked above).
Known constraints (measured, not speculative)
arc-signerfails--no-default-featuresby design — itscompile_error!("At least one signing provider feature must be enabled")guard is correct code (genuine either/or betweenlocal/remoteproviders), but cargo-hack counts it as a failure. Hence the split invocation above;--at-least-one-of local,remotekeeps every other combination of its 5 features covered instead of exempting the crate entirely.- Cost: full matrix is 64 configurations, ~69 min cold-cache local (@mehmetkr-31's measurement). Only 8 of 25 crates declare features; if the warm-cache CI number is still too slow for per-PR, the fallback is scoping to feature-declaring crates or running the matrix on merge-queue/nightly rather than every push. Suggest measuring one warm run before deciding.
- Baseline: goes green only after fix(types): make the arbitrary feature self-contained #231 and fix(types): make malachitebft-signing-ed25519 a required dependency #237 both land — the job's first run doubles as regression proof for both fixes.
Acceptance
- Job red on
mainprior to fix(types): make the arbitrary feature self-contained #231/fix(types): make malachitebft-signing-ed25519 a required dependency #237, green after both merge. - Reverting either fix turns the job red (the two bugs are the test fixture).
@mehmetkr-31 offered to write the workflow PR (thanks for the matrix run and the arc-signer catch) — filing this per the division of labor agreed on #233.
Activity
osr21 commentedon Aug 12, 2026
Evidence update from #232 (comment): mehmetkr-31 reproduced #233's
E0433on the pinned 1.91.1 toolchain (cargo clippy -p arc-consensus-types --all-targets --all-featuresfails there exactly as on 1.92).Two consequences for this proposal:
The blind spot is confirmed toolchain-independent. bug: arc-consensus-types
arbitraryfeature does not build standalone #233's failure is a missing feature declaration hidden purely by package-selection-scoped feature unification — no toolchain bump would ever have surfaced it. Meanwhile chore(types): drop redundant macro imports that break CI on Rust 1.92 #232/ci: Rust Lint job breaks on Rust 1.92, blocking a toolchain bump past the pinned 1.91.1 #234's failure is the opposite: toolchain-sensitive, scope-insensitive. The two shipped bug classes fail on orthogonal axes, and current CI covers neither axis at per-crate scope. That is precisely the gapcargo hack --each-featurecloses.The proposed job needs no toolchain caveat and comes with a built-in acceptance test. Because the bug: arc-consensus-types
arbitraryfeature does not build standalone #233 repro holds on today's pin, the job would go red immediately onmainas it stands (until fix(types): make the arbitrary feature self-contained #231 merges), which is correct behavior — landing fix(types): make the arbitrary feature self-contained #231 first gives a green baseline, or landing this job first demonstrates it catches a real shipped bug on day one. Either ordering works; the former is less noisy.Verification trail: 1.91.1 run measured by mehmetkr-31 (linked comment), mechanism analysis in the same thread.
ci: add per-crate feature matrix check with cargo-hack
mehmetkr-31 commentedon Sep 18, 2026
Requesting assignment under the new contribution policy — #241 already implements this. osr21 filed the issue and handed the workflow over to me, so assigning me keeps that split. It was opened before #282 landed; happy to close it and open a fresh one once assigned, if you'd prefer.