105 lines
5.8 KiB
Markdown
105 lines
5.8 KiB
Markdown
# 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 checked as the implement stage verified them; deviations are noted
|
|
inline and in `plan/summary.md`.
|
|
|
|
## Store layout and de-duplication (D2, D4)
|
|
|
|
- [x] 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()`.
|
|
- [x] Every stored audio file has a paired `<name>.cbd-store.toml` sidecar with
|
|
a `hash` and at least one `[[provider]]` entry; the sidecars are the only
|
|
persisted index (no separate index file).
|
|
- [x] `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).
|
|
- [x] 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).
|
|
- [x] 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.
|
|
- [x] 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).
|
|
- [x] The download byte budget counts only bytes fetched this run; provider-id
|
|
and hash hits cost zero budget.
|
|
- [x] 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)
|
|
|
|
- [x] `[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.
|
|
- [x] 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.
|
|
- [x] `from_track_store` round-trips: the written toml re-reads as
|
|
`Playable::Store(name)` and `to_track` sets `is_captured = true` for it.
|
|
- [x] 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)
|
|
|
|
- [x] `Track.provider_item_id` is set by tidal (track id) and youtube (video
|
|
id), stable across the paths an item is reached by (search vs playlist vs
|
|
album). **fs leaves it empty by design** — fs sources de-duplicate by content
|
|
hash instead (hashing a local file is cheap and `to_track` has no disk
|
|
context to canonicalize a relative `file` playable); "already in the store →
|
|
no-op" still works via the store-root path check.
|
|
- [x] The store keys on `(provider, id)` where provider is the source track's
|
|
path root — two different provider ids never collide across providers.
|
|
- [x] `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)
|
|
|
|
- [x] `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.
|
|
- [x] A save to an existing `/crabidy/<name>` is refused with a clear warning
|
|
and changes nothing on disk; the user must delete and re-save.
|
|
- [x] 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).
|
|
- [x] `current` is reserved: the playback loop overwrites it on every queue
|
|
change; a user save named `current` is rejected. Saved queues are flat.
|
|
- [x] Queue `w` link-saves the live queue into `/crabidy/<name>` and queue `W`
|
|
captures it via `CaptureLibraryNode` on `/crabidy/current`; both refuse an
|
|
existing name. (The `SaveQueue` RPC was kept as the queue-`w` entry point and
|
|
reimplemented server-side as a link save — see `plan/summary.md` deviations —
|
|
rather than removed as D6 proposed.) Capture progress streams via
|
|
`CaptureProgress` (unchanged shape).
|
|
|
|
## UI (D8)
|
|
|
|
- [x] The root library shows a single `crabidy` child; `queues`/`bookmarks`/
|
|
`captures` no longer appear.
|
|
- [x] 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). Node/child
|
|
marking is shallow: a node is marked captured when all its tracks are
|
|
captured and it has no child nodes (flat saves); a nested save's top folder
|
|
is not marked — full-recursion marking is future work.
|
|
- [x] 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)
|
|
|
|
- [x] 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.
|
|
- [x] External calls (downloads) keep their timeouts; the capture runs on a
|
|
spawned task so the orchestrator keeps serving commands.
|
|
- [x] Store mutation is serialized (the index mutex) so concurrent captures
|
|
cannot corrupt a sidecar or race the numeral-suffix naming.
|
|
- [x] Stream URLs stay redacted (scheme/host only) in any new log lines; store
|
|
names and titles are fine to log, tokens are not.
|