crabidy/quality/queue-order.md

125 lines
6.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Quality gates: queue de-duplication and sorting
Criteria an implementation of `architecture/queue-order.md` must satisfy.
Automatic tests cover the behaviour (see "Tests" at the end); the gates here
are the things a reader has to check by reading the code. Each is pass/fail.
## Correctness of the operations
- [ ] **G1 — Dedup never removes the playing entry.** In every group of
duplicates the survivor is the entry at the current position when it is in
the group, otherwise the earliest one (D4). Check the survivor selection
directly, not only through a test: a "keep the first" shortcut is a silent
playback stop the moment the playing copy is a later one.
- [ ] **G2 — The default duplicate identity is provider-scoped.** The key is
`(first path segment, provider_item_id)` when the id is non-empty and the
whole path otherwise (D3). Artist/title matching exists **only** under the
opt-in `by_title` identity (D3a), never on the default path, and the flag is
never defaulted to true anywhere between key and wire.
- [ ] **G2a — `by_title` keeps the longest take** and still yields to the
playing entry (D3a), and every client surface that reaches it is distinct
from the safe one (its own key, its own flag) — a user cannot get it by
mistyping.
- [ ] **G3 — Sort is stable and total.** `sort_permutation` returns a
permutation of `0..len` for every strategy and direction, equal keys keep
their queue order, and blanks/unknown durations sort last in *both*
directions (D7, D8). No comparator may panic (no `unwrap` on a partial
comparison, no float keys).
- [ ] **G4 — Sort keys are computed once per track.** Decoratesortundecorate,
not a comparator that lowercases inside the comparison (A5). A ten-thousand
entry queue must not allocate per comparison.
- [ ] **G5 — Reverse ignores the direction flag** (D6), and
`QueueSort::Unspecified` reaching `QueueManager::sort` is a no-op rather
than an arbitrary default.
- [ ] **G6 — The play-order invariant holds after both operations.**
`play_order` indexes every track exactly once and `current_offset` points
into it (the invariant `shuffle_insert_keeps_order_unique` guards for
inserts). Shuffle off ⇒ rebuilt from the new order; shuffle on ⇒ remapped
through the permutation, so the remaining play sequence is unchanged (D10).
- [ ] **G7 — Dedup goes through `remove_tracks`.** There is exactly one place
that removes queue entries (D5). A second removal implementation that
maintains `play_order` itself is a fail even if its tests pass.
## Server discipline
- [ ] **G8 — Both operations run on the playback loop and nowhere else.** No
queue mutation outside a `PlaybackCommand` arm; the queue lock is never held
across an `await` (the pattern every existing arm follows).
- [ ] **G9 — One broadcast site.** Both go out through `broadcast_queue`, so
the persister and the update stream cannot drift (D12). A dedup that removed
nothing broadcasts nothing.
- [ ] **G10 — No panics on client input.** `SortQueue` with an unset or
unknown enum value answers `InvalidArgument`; `DedupQueue` with a dropped
result receiver logs and continues; an empty queue is a no-op for both. No
`unwrap`/`expect`/indexing that a client can reach.
- [ ] **G11 — Both RPCs are in the rights matrix as `QueueOwner`** (D1), and
the pinned method-list test in `auth.rs` includes them. A new RPC must not
be able to reach the wire unmapped.
- [ ] **G12 — The dedup count crosses the boundary as a number.** No
server-formatted human string on the wire or in a `MessageToUi` (D2, D15);
wording belongs to each client.
- [ ] **G13 — Nothing is logged that could carry a credential.** Sort logs the
strategy, dedup logs a count. No track paths, no URLs — a queue entry's path
can be a `/rss` item whose URL is a per-subscriber token.
## Clients
- [ ] **G14 — No client computes an order.** No comparators, no
duplicate-finding, and no `Remove`-derived dedup in `cbd-tui`, `cbd-web` or
`cbd-cli` (A1, Option A rejected). Clients send the verb and render what
comes back.
- [ ] **G15 — The sort menu is strictly modal.** While it is open the bindings
table is unreachable and an unclaimed key leaves it open rather than falling
through — a stray `c` must not clear the queue while the user thinks they are
picking a sort (D14).
- [ ] **G16 — Both operations are reachable without a keyboard in the web
client** (D16): toolbar buttons dispatching the same actions as `u` and `S`,
and the strategy list exists once (`SORT_CHOICES`), driving both the overlay
and the button.
- [ ] **G17 — The TUI and web keymaps agree**: `u` = dedup, `S` = sort menu,
and the same five menu letters with the same uppercase-is-descending rule.
Where they differ, the difference is deliberate and documented.
- [ ] **G18 — Feedback exists for both outcomes of a dedup.** `0` renders as
plainly as `7` (D2/D15) — a dedup that found nothing must not look like a
dropped keypress.
- [ ] **G19 — The CLI strategy is a `ValueEnum`** (D17), so a typo is a parse
error, the shell completes it, and the printed confirmation names the
strategy in the same spelling the user typed.
- [ ] **G20 — Every new binding is documented where the others are**: the TUI
help modal (derived from `BINDINGS`, so automatic), the web help overlay
table, `docs/src/clients/tui.md`, `docs/src/clients/cli.md`, and
`docs/src/queue.md`'s queue-operation list.
## Documentation and honesty
- [ ] **G21 — The proto documents the semantics at the wire.** What a
duplicate is, which copy survives, that unknowns sort last, that `Reverse`
ignores `descending`, and that `UNSPECIFIED` is refused.
- [ ] **G22 — The known gaps are written down, not left to be discovered**: a
captured copy and its streaming source are not duplicates (D3), a sort with
shuffle on does not change what plays next (D10), and tracks still resolving
land unsorted after the fact (D11).
- [ ] **G23 — Public items have doc comments** stating intent and edge
behaviour, in the register the surrounding code uses (why, not what).
## Tests
Behaviour is pinned by these, all of which must pass:
| Area | Test home | Pins |
| --- | --- | --- |
| Identity, survivors, permutations | `queue_order.rs` | D3, D4, D6D9 |
| Play-order invariants | `lib.rs` (`QueueManager`) | D5, D10, empty queues |
| Wiring, broadcast, persistence | `playback.rs` | D2, D9, D12 |
| Argument validation | `rpc.rs` | D6, the count |
| Rights matrix | `auth.rs` (existing pinned test) | D1 |
| Keys and modality | `cbd-tui/src/app/{bindings,sort,mod}.rs` | D14 |
| Pane feedback | `cbd-tui/src/app/queue.rs` | D15 |
| Command parsing | `cbd-cli/src/lib.rs` | D17 |
| Keymap parity | `cbd-web/src/keymap.rs` | D16, D17 |
The server rows live under `crabidy-server/src/`.
Plus the repository's standing gates: `cargo clippy` clean, `cargo fmt`,
no new `unwrap` on client-reachable paths, and the book building.