96 lines
4.9 KiB
Markdown
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.
|