Skip to content

ci(gate): compile and lint feature-gated code, which nothing did (#1380) - #1423

Merged
dc0sk merged 1 commit into
mainfrom
fix/1380-feature-rot-guard
Sep 20, 2026
Merged

dc0sk merged 1 commit into
mainfrom
fix/1380-feature-rot-guard

Conversation

@dc0sk

@dc0sk dc0sk commented Sep 20, 2026

Copy link
Copy Markdown
Owner

Closes #1380.

#[cfg(feature = "x")] code must still parse when the feature is off, so a syntax error was
caught — but nothing after parsing was: type errors, borrow errors, wrong arity, a renamed method.
Both lint passes in gate.sh and the hook build --no-default-features, so nine feature families
gating code
(cpal, serial/gpio, gpu, keychain, tokio, serde, gui/serve, hardware-tests, instruments)
were compiled by no automated check. ci.yml's gpu-feature-gates guarded one of them and, being
release/**-scoped (#1120), never ran on an ordinary PR.

It found two live defects, neither in cpal

  • apps/openpulse-linksim/tests/serve_integration.rs — E0063. LinkParams gained cessb_enabled
    (b883b5f8) and notch (26696f98) in 2026-06; the test was last touched 2026-06-21. The serve
    feature's only tests had been uncompilable, and therefore never run, for ~3 months. They now
    run: 2 passed, 0 failed.
  • apps/openpulse-linksim/src/gui.rs:509 — float-literal-f32-fallback, which rustc says "will
    become a hard error in a future release"
    . The gui binary would have stopped compiling on a
    future toolchain with no warning to anyone.

One rule, not a list

cargo clippy --workspace --all-features --all-targets -- -D warnings, rather than the issue's named
--features cpal-backend: a hand-maintained list of crate+feature pairs is the same rotting mirror
the guard exists to catch. Safe because every feature here is additive (generic-serial = ["serial"]). A per-feature matrix was considered and rejected on measurement — no
cfg(all(feature = A, not(feature = B))) exists anywhere, so a 2¹⁷ powerset would find zero defects.

The preflight is not ceremony

--all-features pulls alsa-sys, libudev-sys and libdbus-sys, whose build scripts call
pkg_config and panic when a .pc file is absent. The ubuntu runner ships none of the three
-dev packages and neither gate-running job installed anything, so as first written this was
red-on-arrival for post-merge-gate.yml — #1074's archetype. My clean local run described this
host only after a root update stamped those .pc files on 2026-09-19; the ledger records
libdbus-sys failing on this same machine four days earlier.

So: a pkg-config preflight in both gate and hook that fails with the Debian package names
rather than skipping, and an apt-get step (deliberately not || true) in both CI jobs that run
gate.sh.

A latent defect the design exposed

gpiocdev is declared under [target.'cfg(target_os = "linux")'] while gpio.rs was gated on
feature = "gpio" alone. Cargo enables a feature whose optional dep is target-filtered out, so
--all-features on macOS compiles that code with no crate. Retargeted to all(target_os = "linux", feature = "gpio") at all five sites. Unverified on darwin — no darwin std on this host.

gpu-feature-gates removed

Subsumed: the new pass covers gpu and hardware-tests across every crate rather than five named
plugins, on every local gate run and in post-merge-gate, where that job never ran at all. Its
reasoning and the PR #424 precedent survive in the new step's comment.

Two stated limits

Not closed over targets (see gpio). Not closed over the shipped recipe: --all-features turns
instruments on, so an instruments-only item called from a cpal-gated production path passes all
three passes and fails only cargo build --release -p openpulse-cli --features cpal-backend. Zero
instances today — a residual, not a claim of completeness. My earlier "2×2 grid" framing was wrong:
the axes are not independent.

Sabotage — #1380's own probe

A planted type error in the cpal-gated run_drive (calibrate.rs:320):

pass1  --no-default-features --all-targets  -> rc=0
pass2  --no-default-features                -> rc=0
pass3  --all-features --all-targets         -> rc=101

The two rc=0 rows are what make the failure attributable to the new pass. The preflight was
separately sabotaged with a nonexistent library and correctly printed SKIPPED (missing pkg-config: zz-nonexistent-lib) with the install line, and failed.

Evidence honesty

Both defects found are in openpulse-linksim, a crate under a 2026-09-10 direction to be replaced,
and the yield on shipped cpal/gpu/serial paths was zero. The justification is the class — three
months undetected, PR #424 precedent — not today's haul.

Test results

scripts/gate.sh on the branch tip, clean tree:

suites=339 tests_passed=2574 tests_failed=0
GATE: PASS bcb23ed5def5098148236a0e1f7175a5f8487400 clean 20260920T093321Z

All 13 steps ok, including the new one.

cargo test -p openpulse-linksim --features serve --test serve_integration → 2 passed, 0 failed.

Reviewer note

The CI evidence that matters here is post-merge-gate.yml, not this PR's checks — the gate does
not run pre-merge (#1144), and the apt step is the thing at risk. I will confirm that run after merge
and report it.

Verification-objective: feature-gated code must be compiled and linted by an automated check, not
only parsed

Review: docs/dev/reviews/review-1380-feature-rot-guard.md

🤖 Generated with Claude Code

https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6

`#[cfg(feature = "x")]` code must still PARSE when the feature is off, so a syntax
error was caught — but nothing after parsing was: type errors, borrow errors, wrong
arity, a renamed method. Both lint passes in gate.sh and the hook build
--no-default-features, so nine feature families gating code (cpal, serial/gpio, gpu,
keychain, tokio, serde, gui/serve, hardware-tests, instruments) were compiled by no
automated check. ci.yml's gpu-feature-gates guarded one of them and, being
release/**-scoped (#1120), never ran on an ordinary PR.

ONE RULE, not the issue's named --features cpal-backend: a hand-maintained list of
crate+feature pairs is the same rotting mirror the guard exists to catch. Safe because
every feature here is additive (generic-serial = ["serial"]). A per-feature matrix was
considered and rejected on measurement — no cfg(all(feature = A, not(feature = B)))
exists anywhere, so a 2^17 powerset would find zero defects.

IT FOUND TWO LIVE DEFECTS, neither in cpal: a `serve`-gated test left behind when
LinkParams gained cessb_enabled (b883b5f) and notch (26696f9) in 2026-06 —
uncompilable, therefore never RUN, for ~3 months — and a float-literal-f32-fallback in
the gui binary that rustc says becomes a hard error. Both fixed; the serve tests now
run: 2 passed, 0 failed.

THE PREFLIGHT IS NOT CEREMONY. --all-features pulls alsa-sys, libudev-sys and
libdbus-sys, whose build scripts call pkg_config and PANIC when a .pc file is absent.
The ubuntu runner ships none of the three -dev packages and neither gate-running job
installed anything, so this was red-on-arrival for post-merge-gate.yml (#1074's
archetype). My clean local run described this host only AFTER a root update stamped
those .pc files on 2026-09-19; the ledger records libdbus-sys failing here four days
before. Both gate and hook now fail with the Debian package names rather than skipping,
and both CI jobs apt-get them (not `|| true`).

A LATENT DEFECT THE DESIGN EXPOSED: gpiocdev is declared under
[target.'cfg(target_os = "linux")'] while gpio.rs was gated on feature = "gpio" alone.
Cargo enables a feature whose optional dep is target-filtered out, so --all-features on
macOS compiles that code with no crate. Retargeted to
all(target_os = "linux", feature = "gpio") at all five sites; unverified on darwin here.

gpu-feature-gates REMOVED, subsumed: the new pass covers gpu and hardware-tests across
every crate rather than five named plugins, on every local gate run and in
post-merge-gate, where that job never ran at all.

TWO STATED LIMITS. Not closed over targets (see gpio). Not closed over the shipped
recipe: --all-features turns `instruments` on, so an instruments-only item called from a
cpal-gated production path passes all three passes and fails only
`cargo build --release -p openpulse-cli --features cpal-backend`. Zero instances today.

Sabotage — #1380's own probe, a planted type error in the cpal-gated run_drive:
  pass1 --no-default-features --all-targets -> rc=0
  pass2 --no-default-features               -> rc=0
  pass3 --all-features --all-targets        -> rc=101
The two rc=0 rows are what make the failure attributable to the new pass.

Evidence honesty: both defects found are in openpulse-linksim, a crate slated for
replacement, and the yield on shipped cpal/gpu/serial paths was zero. The justification
is the class (3 months undetected, PR #424 precedent), not today's haul.

Verification-objective: feature-gated code must be compiled and linted by an automated
check, not only parsed

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6
@dc0sk
dc0sk merged commit 913959a into main Sep 20, 2026
11 checks passed
@dc0sk
dc0sk deleted the fix/1380-feature-rot-guard branch September 20, 2026 10:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature-gated (cpal) code is never type-checked: the gate and pre-push hook both build --no-default-features

1 participant