crabidy/quality/progressive-queueing.md

96 lines
4.9 KiB
Markdown

# Quality gates — progressive queueing
Criteria for `architecture/progressive-queueing.md`. Automatic tests live in
`crabidy-core/src/lib.rs` (default `resolve_tracks_into` walk, fake
provider), `crabidy-server/src/lib.rs` (`PendingResolve` chunk application),
`tidaldy/src/lib.rs` (offline path validation), and
`cbd-tui/src/app/queue.rs` (indicator rendering). They fail while the stubs
are unimplemented — that is the target state for `implement`.
Run: `devenv shell -- cargo test --workspace` (session-local
`CARGO_TARGET_DIR` when `target/` is owner-built).
## Channel and concurrency gates (verify by reading)
- [x] **All new channels are bounded.** The chunk channel is small (≤ 8
chunks); forwarder → playback reuses the existing `bounded(64)`. No
`unbounded()` anywhere in the feature.
- [x] **No lock is held across an `await`.** The `pending` map and `queue`
mutexes are locked, used, and released inside synchronous blocks only
— same discipline as the existing playback handlers.
- [x] **The playback loop stays the single writer of queue state.** Neither
the forwarder task nor the provider resolve task touches
`QueueManager` or broadcasts; they only send commands.
- [x] **The provider loop never blocks on a resolve.** The `ResolveTracks`
arm spawns; `GetTrackUrls` for the first chunk's track must be
servable while a resolve is still streaming (deadlock check: playback
awaiting `GetTrackUrls` + provider awaiting a chunk send must be
impossible).
- [x] **Channel semantics are documented where they are the contract**:
sender-drop = finished, receiver-drop = cancel, on both
`ProviderClient::resolve_tracks_into` and
`ProviderCommand::ResolveTracks`.
## Cancellation gates
- [x] **`Replace` and `Clear` cancel every pending op** before mutating the
queue; `Queue`/`Append`/`Insert` cancel nothing.
- [x] **Cancellation propagates to the network fetch**: the forwarder drops
the chunk receiver, and both resolve implementations (default walk and
tidaldy override) treat a failed chunk send as "stop fetching, return
Ok" — verified by test for the default, by reading for tidaldy's page
loop.
- [x] **Late chunks for finished/cancelled ops are dropped silently** (no
error, no queue mutation).
## Behavior gates
- [x] **Immediate feedback**: accepting a resolve op broadcasts a `Queue`
snapshot with `resolving = true` before the first chunk arrives.
- [x] **`resolving` is true iff at least one op is pending**, and every
queue broadcast from the playback loop goes through the one helper
that sets it (grep: no direct `StreamUpdate::Queue(` construction in
the op/chunk paths besides `broadcast_queue`).
- [x] **Playback starts with the first chunk** that makes a track current
(replace, or landing in an empty queue) and never restarts for later
chunks of the same op — covered by the `PendingResolve` tests, plus
reading the `apply_resolved_chunk``play_if_some` wiring.
- [x] **Order preserved end-to-end**: pages arrive in collection order,
nodes in listing order (pre-order walk — the old LIFO reversal must
not reappear), multi-path requests resolve sequentially in request
order, and `InsertAfter` keeps the whole op contiguous.
- [x] **An op that finishes with zero tracks logs the existing
"resolved to no playable tracks" warning** with enough context (paths).
## Error and robustness gates
- [x] **No panics on provider failure, in any spawned task.** Unreadable
nodes are skipped with a warning; a dead provider channel ends the op
(with `ResolveFinished` still sent, so `resolving` clears) instead of
leaving a stuck indicator.
- [x] **Tracing spans survive the spawns**: forwarder and provider resolve
tasks attribute their events to the originating request's span
(`ProviderMessage::new` capture + `in_current_span`/explicit parent).
- [x] **tidaldy page loop honors the existing token/refresh path** (reuses
`make_request`) and adds no new retry logic.
## Wire and UI gates
- [x] **Proto change is additive only**: `Queue.resolving = 4`, no rpc shape
changes; field documented in the proto.
- [x] **The TUI pseudo-item is render-only**: never in the list model,
unreachable by selection/removal/navigation (`get_size` unchanged) —
covered by tests.
- [x] **Indicator style matches the pane** (`COLOR_SECONDARY`, inside the
queue block, after the last track).
- [x] **Old client / new server and new client / old server both degrade
cleanly** (flag ignored / never set — reasoning check against the
generated proto defaults).
## Documentation gates
- [x] **`ProviderClient::resolve_tracks_into` docs state the full channel
contract** (order, completion, cancellation, error policy).
- [x] **Deviations from the architecture are recorded** in
`plan/summary.md` under this feature.