# Quality gates: the `crabidy` provider and content store Criteria the implementation must satisfy beyond the automatic tests (`fsdy/src/lib.rs`, `crabidy-server/src/crabidy_store.rs`, and the TUI tests). Each gate is pass/fail by reading the code. See `architecture/crabidy-store.md`. Boxes are unchecked until the implement stage verifies them. ## Store layout and de-duplication (D2, D4) - [ ] The content store is a flat directory under `dirs::data_dir()/crabidy`; the toml tree is under `dirs::state_dir()/crabidy`. Neither uses `dirs::config_dir()`. - [ ] Every stored audio file has a paired `.cbd-store.toml` sidecar with a `hash` and at least one `[[provider]]` entry; the sidecars are the only persisted index (no separate index file). - [ ] `StoreIndex` is built by scanning the sidecars at open and updated on every write; lookups go through it (no per-capture directory rescans, no shelling out to grep/rg). - [ ] Capturing a track already present by `(provider, provider_item_id)` does **no** download and reuses the existing store entry; a differing title is appended to that entry's `aliases` (not duplicated). - [ ] Capturing content already present by hash (provider id missed) discards the freshly fetched bytes, adds a new `[[provider]]` entry to the existing sidecar, and points the toml at the existing store name — no duplicate audio. - [ ] A genuinely new track creates one audio file + one sidecar; on a natural-name collision with *different* content the name gets a numeral suffix (never overwrites unrelated audio). - [ ] The download byte budget counts only bytes fetched this run; provider-id and hash hits cost zero budget. - [ ] An `/fs` track whose file is already under the store root captures as a no-op reuse; one pointing at a normal file is **copied** into the store and the original file is left in place. ## `Playable::Store` and fsdy (D2, D7) - [ ] `[playable]` validation is exactly-one across `file`/`url`/`link`/`store`/ `skipped=true`; a `store` value with a path separator or empty is `StoreName`, not accepted. - [ ] A `store` playable resolves only against the instance's `store_root`; an instance without a store root (any non-`/crabidy` mount) treats it as a malformed reference, never a path escape. - [ ] `from_track_store` round-trips: the written toml re-reads as `Playable::Store(name)` and `to_track` sets `is_captured = true` for it. - [ ] Deleting a `/crabidy` track removes only its toml; deleting a folder removes only the toml folder. Store audio (outside the toml root) is never removed — verified via the existing "audio must be under the instance root" guard. ## Provider identity (D3) - [ ] `Track.provider_item_id` is set by tidal (track id), youtube (video id), and fs (canonical source path); it is stable across the paths an item is reached by (search vs playlist vs album). - [ ] The store keys on `(provider, id)` where provider is the source track's path root — two different provider ids never collide across providers. - [ ] `provider_item_id` is never logged as a secret and carries no token; it is an opaque provider id only. ## Save, conflict, current (D5, D6) - [ ] `w` writes link tomls (no store, no audio); `W` writes store-backed tomls and runs the de-dup capture. Both work on a library node and on the queue. - [ ] A save to an existing `/crabidy/` is refused with a clear warning and changes nothing on disk; the user must delete and re-save. - [ ] Saves are atomic: built in a hidden temp sibling and swapped into place on success; a failed/crashed save leaves no partial top-level folder, and any audio already committed to the store persists (making retry cheap). - [ ] `current` is reserved: the playback loop overwrites it on every queue change; a user save named `current` is rejected. Saved queues are flat. - [ ] `SaveQueue` is gone; queue `w`/`W` go through `CaptureLibraryNode` on `/crabidy/current`. Validation errors return synchronously; progress streams via `CaptureProgress` (unchanged shape). ## UI (D8) - [ ] The root library shows a single `crabidy` child; `queues`/`bookmarks`/ `captures` no longer appear. - [ ] Captured rows are prefixed with `|` as the first character of the row (before selection padding), driven by `is_captured`; captured tracks are marked even while browsing another provider (tidal/youtube). - [ ] Deletion on `/crabidy` has no confirmation dialog and no disk-reclamation path (the `capture-deletion.md` confirm flow is removed). ## Errors and safety (always-on rules) - [ ] No panics on malformed sidecars, missing store files, partial downloads, or unreadable sources — every defect is a typed error and a bad sidecar is skipped with a warning, never poisoning the index. - [ ] External calls (downloads) keep their timeouts; the capture runs on a spawned task so the orchestrator keeps serving commands. - [ ] Store mutation is serialized (the index mutex) so concurrent captures cannot corrupt a sidecar or race the numeral-suffix naming. - [ ] Stream URLs stay redacted (scheme/host only) in any new log lines; store names and titles are fine to log, tokens are not.