headroom/crates
atagen 4c39ecd5d2 fix: route.set matches application_name too, not just process_binary
The `route.set <app> <route>` overlay used to emit a single
`RouteRule` keyed on `process_binary`. The matcher ANDs across
non-empty fields, so a stream that didn't advertise
`application.process.binary` would miss the rule even though
its `application.name` was a perfect match. pw-cat is the
canonical hit — it sets `application.name = "pw-cat"` and
`node.name = "pw-cat"` but leaves `process.binary` unset
entirely. The same goes for several Electron and Flatpak
wrappers where the wrapping process eats the binary name.

`apply_route_overrides` now emits TWO rules per override, one
keyed on each identity field, with the same route. PipeWire
iterates rules in order and returns on first match, so the
effect is an OR across `process_binary` and `application_name`
for the single override — exactly the "match by whatever name
the stream advertises" intent of the CLI verb.

Why two rules and not "loosen the matcher to OR these two
fields": the matcher's AND-across-fields is load-bearing for
profile-author rules like `{process_binary: ["firefox"],
media_role: ["voice"]}` (match firefox-with-voice-role only).
Loosening the matcher would silently break those. Two
single-field rules with the same route preserve the original
semantics and add zero risk.

`is_single_app_rule_for_any` (the retain pre-pass that drops
old override rules before re-emitting) extends to recognise
the application_name-only variant too, so re-setting or
unsetting an override leaves no residual rules.

Tests
  - `profile_store::tests::set_route_emits_both_process_binary_and_application_name_rules`
    asserts both variants exist after `set_route`.
  - `profile_store::tests::set_route_then_unset_leaves_no_residual_rules`
    catches the matching retain-pre-pass regression that would
    have leaked rules on unset.
  - `routing::tests::application_name_only_rule_matches_stream_with_no_process_binary`
    proves a stream with `application.name = "pw-cat"` and no
    `process.binary` actually matches the application_name-keyed
    rule path. 194 tests pass (was 191; +3 for the new
    coverage); clippy clean.

Live verification

  Daemon up, pw-cat → headroom-processed (default rule).
  `headroom route set pw-cat bypass`: pw-cat's link snaps to
  `Mbox:playback_FL` within one drain tick (~50 ms); status
  reports `route: bypass`. Layer A tap survives the transition
  intact. `headroom route unset pw-cat`: snaps back to
  `headroom-processed:playback_FL`. Both transitions are
  audibly clean against the F2 audio-gap mitigation from
  `5c769a1`.
2026-05-21 21:12:23 +10:00
..
headroom-cli 8e: playback callback timing instrumentation + spike investigation 2026-05-21 16:42:46 +10:00
headroom-client stage 2 2026-05-19 16:33:09 +10:00
headroom-core fix: route.set matches application_name too, not just process_binary 2026-05-21 21:12:23 +10:00
headroom-dsp filter rate matching A+B: runtime-parameterised rate at boot 2026-05-21 20:43:55 +10:00
headroom-ipc filter rate matching A+B: runtime-parameterised rate at boot 2026-05-21 20:43:55 +10:00