Skip to content

ci: per-crate feature matrix check (cargo hack --each-feature) to close the workspace-unification blind spot #240

Open

Description

@osr21

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 #236

Two shipped bugs prove the class (writeups: #233, #236, and the verification matrix in #233 (comment)):

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,remote

cargo-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)

  1. arc-signer fails --no-default-features by design — its compile_error!("At least one signing provider feature must be enabled") guard is correct code (genuine either/or between local/remote providers), but cargo-hack counts it as a failure. Hence the split invocation above; --at-least-one-of local,remote keeps every other combination of its 5 features covered instead of exempting the crate entirely.
  2. 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.
  3. 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

@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

  1. osr21 commented on Aug 12, 2026

    @osr21
    Author

    Evidence update from #232 (comment): mehmetkr-31 reproduced #233's E0433 on the pinned 1.91.1 toolchain (cargo clippy -p arc-consensus-types --all-targets --all-features fails there exactly as on 1.92).

    Two consequences for this proposal:

    1. The blind spot is confirmed toolchain-independent. bug: arc-consensus-types arbitrary feature 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 gap cargo hack --each-feature closes.

    2. The proposed job needs no toolchain caveat and comes with a built-in acceptance test. Because the bug: arc-consensus-types arbitrary feature does not build standalone #233 repro holds on today's pin, the job would go red immediately on main as 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.

  2. added a commit that references this issue on Sep 7, 2026
    f3fb979
  3. mehmetkr-31 commented on Sep 18, 2026

    @mehmetkr-31

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions