crabidy/quality/queue-persistence.md

93 lines
4.2 KiB
Markdown

# Quality gates: queue-persistence
Criteria the implementation must satisfy beyond the automatic tests
(`fsdy/src/lib.rs`, `crabidy-server/src/queue_store.rs`, plus the playback
and TUI tests added during implementation). Each gate is pass/fail by
reading the code.
## fsdy instances and serialization
- [x] Every occurrence of the hardcoded `"/fs"`/`"/fs/"` prefix inside
`fsdy::Client` methods now derives from the instance's `provider_root`;
`PROVIDER_ROOT` remains only as the default instance's constant
(`init`) and for external callers.
- [x] Path traversal validation still happens in exactly one place
(`disk_path`) and applies to every instance.
- [x] `TrackFile::from_track` is the single Track→file conversion site,
and it always emits a `link` playable (no special cases per provider).
- [x] Serialization never panics: `to_toml` returns
`TrackFileError::Serialize`, and every `Option` field is
skip-serialized (TOML cannot represent `None`).
- [x] The one-hop link argument holds in code: `get_urls_for_track` still
returns `MalformedPath` for a link playable, so removing the
`LinkIntoFs` rejection cannot introduce recursion anywhere.
## Queue store
- [x] `QueueStore` is the only writer of the queues directory; the
`/queues` provider instance only reads.
- [x] Writes are tmp-and-swap: entries are written to a hidden
(dot-prefixed) temp sibling, then the old folder is removed and the
temp renamed. No code path writes entries into the live folder
directly.
- [x] `save` validates the name first and never touches disk for an
invalid name or empty snapshot.
- [x] `load_current` never fails the server: missing folder → `None`,
broken entry → skip with a warning naming the file (never its
contents), broken sidecar → default state with a warning.
- [x] File names come from `fsdy::track_file_name` — no second naming
scheme.
- [x] No file contents in logs anywhere in the store (paths and names
only).
## Playback wiring
- [x] The playback loop never blocks on disk: auto-persist goes through
the `watch` channel (latest wins), `SaveQueue` writes on a spawned
task that reports back through the command's result channel.
- [x] Every queue-state change reaches the persist channel: queue
content changes (the `broadcast_queue` funnel), current-track changes
(`play`), and the shuffle/repeat toggles.
- [x] The persister task debounces and skips snapshots equal to the last
one written (pure `resolving`-flag broadcasts must not rewrite the
folder).
- [x] Persist failures are warnings; no persist error can stop playback
or crash the loop.
- [x] The startup restore runs before the playback loop serves commands,
restores tracks + position + repeat/shuffle, and never starts
playback (`PlayState::Stopped`).
- [x] Restore tolerates a corrupt position (out of range → clamped or
reset, never a panic).
- [x] A server without a usable queues directory (no config dir, mkdir
fails) runs without persistence after a warning — never dies.
## RPC and orchestrator
- [x] `save_queue` maps errors: invalid name → `invalid_argument`, empty
queue → `failed_precondition`, I/O → `internal`; no `color-eyre`/debug
reports leak to clients.
- [x] The orchestrator routes `/queues` in **every** `ProviderClient`
method (same completeness as `/fs`), and `get_lib_root` lists the
`queues` child only when the instance exists.
- [x] `/queues` mutations via the library stay `NotSupported`
(create/rename/delete unchanged).
## TUI
- [x] `w` is bound in `Scope::Queue` only, has a help description, and
passes the existing bindings-table invariant tests unchanged.
- [x] The save overlay reuses `InputState` (Esc cancels, Enter submits
trimmed, empty submit closes silently) and is a no-op while the queue
is empty.
- [x] `MessageFromUi::SaveQueue` reaches the `SaveQueue` RPC; a failed
save must not crash the TUI.
## Hygiene
- [x] New public items are documented; docs state error/edge behavior.
- [x] `clippy -D warnings`, `fmt`, `taplo`, `markdownlint` clean on the
whole workspace; all tests green.
- [x] `architecture/fs-provider.md` reconciled: the "no links into /fs"
rule replaced by the one-hop semantics, D2's "chains structurally
impossible" wording updated.