# Quality gates — MPRIS Criteria an implementation of `architecture/mpris.md` must satisfy. Automated coverage lives in `cbd-tui/src/mpris.rs` (the mapping, as unit tests) and `cbd-tui/tests/mpris_bus.rs` (the round trip over a real session bus, skipped when there is none). ## Hard rules (highest priority) - [x] **G1 — No panic, whatever the bus does.** A missing session bus, a bus that never answers, a refused name, a failed property emission and a dropped connection are all logged and survived (D13). The connection attempt carries a timeout, as an external call must. *(A missing bus is exercised by every test run: `start` returns `None` and the client runs on.)* - [x] **G2 — No secret and no credential-bearing URL reaches the bus.** Published metadata is title, artist, album, length and a synthetic trackid — no `xesam:url`, no `mpris:artUrl`, no library path (D8). Every peer on a session bus can read properties, so this is a redaction boundary, not a matter of taste. *(tests: `metadata_names_the_track_and_never_a_url`, and the bus test asserts the library path is absent from what a client actually receives.)* - [x] **G3 — The state feed is bounded.** 64 slots, `try_send`, a `debug!` on overflow; the orchestrator never awaits the bus (D12). *(test: `a_full_feed_drops_instead_of_blocking`.)* - [x] **G4 — No lock is held across an `await`.** The published mirror sits behind a `std::sync::Mutex` whose guard never crosses a suspension point; a poisoned lock is recovered rather than propagated as a panic. - [x] **G5 — Nothing is published that the server did not say.** No optimistic local write on a method call, no interpolated position, no cached duration carried across a track change (D2). ## Behaviour - [x] **G6 — A pause key cannot start playback.** `Play`, `Pause` and the modifier setters consult the published state and send nothing when the target is already the current value; only `PlayPause` toggles unconditionally (D3). *(tests: `pause_never_starts_playback_and_play_never_stops_it`, `play_pause_always_toggles`, `the_modifiers_toggle_only_when_they_differ`.)* - [x] **G7 — Mute round-trips.** Volume 0 mutes rather than turning the level down, so unmuting restores it; a non-zero target unmutes and then moves by the delta (D4). *(tests: `volume_is_muted_as_zero_and_restored_by_unmuting`, `setting_a_volume_sends_the_delta_to_it`.)* - [x] **G8 — Unrepresentable requests are refused, not approximated.** `LoopStatus::Track`, a playback rate other than 1.0, `OpenUri`, `Quit`, `Raise` and `SetFullscreen` answer `NotSupported` and change nothing (D6, D14). *(test: `a_single_track_loop_is_refused_not_faked`.)* - [x] **G9 — An unknown duration is omitted, not zero.** A zero `mpris:length` draws a full progress bar in consumers (D9). *(tests: `an_unknown_duration_is_omitted_not_zero`, `the_live_duration_wins_over_the_tracks_own`.)* - [x] **G10 — `Seeked` fires on a discontinuity and not on the clock or a track change** (D10). *(test: `a_position_jump_is_a_seek_but_a_new_track_is_not`.)* - [x] **G11 — Only real changes are announced.** A `PropertiesChanged` for a value that did not change wakes every listener for nothing. *(test: `only_real_changes_are_announced`.)* - [x] **G12 — A stale `SetPosition` is ignored.** The spec's trackid argument exists so a seek aimed at a track that has since changed does not move the one now playing. *(test: `set_position_becomes_a_delta_and_ignores_a_stale_track`.)* ## Build and shape - [x] **G13 — The feature carries its own weight and nothing else's.** `mpris` adds one crate and no system library; `--no-default-features` compiles, tests and clippies clean through a stub of the same shape, and the matrix in `check-features` covers `mpris` alone, `notifications` alone, and neither. - [x] **G14 — The server is untouched.** No proto change, no server change; the feature is a client-side translation or it is misplaced. - [x] **G15 — Verified against a real client, not only a test double.** `playerctl` must list the player, read its metadata, and drive play-pause, seek and volume. Re-check on a `mpris-server`/`zbus` bump.