# Quality gates — audiobookshelf provider Criteria an implementation of `absdy` (architecture/audiobookshelf-provider.md) must satisfy. Each is pass/fail by reading/reasoning; automated coverage lives in `absdy/src/tests.rs` (unit, no network) and `absdy/tests/live.rs` (ignored, real server). ## Secrets (hard rule — highest priority) - [x] **G1 — `api_key` never in `Debug`.** `Settings` and `Client` `Debug` output redact the key. *(test: `settings_debug_redacts_the_api_key`.)* - [x] **G2 — The token never reaches a log or error.** The `?token=` stream URL is built only in `Abs::stream_url` and returned; it is never passed to `debug!`/`warn!`/`error!`, and never to a `reqwest` call inside `absdy` (so it cannot appear in a `reqwest` error). Browse URLs (logged at `debug`) carry auth in the bearer header, not the URL. - [x] **G3 — `AbsApi::Debug` redacts the token** (manual impl, not derived). ## Errors and robustness (hard rule: no panics on input/network) - [x] **G4 — No panics on bad input or network failures.** All backend failures map to a typed `ProviderError` (`FetchError` for fetches, `MalformedPath` for bad paths, `InvalidInput` for empty titles, `Config` for missing creds). No `unwrap`/`expect`/`panic!` on request, path, or response data. *(tests: `backend_failures_are_typed_never_panics`, `foreign_and_malformed_paths_are_rejected`, `track_metadata_picks_the_file_by_ino`.)* - [x] **G5 — Defensive decoding.** Wire DTOs use `#[serde(default)]`; missing fields degrade (empty string / `None` / dropped entry), never error the whole call. Items/files without an id/ino are dropped, not panicked on. - [x] **G6 — Timeouts on every external call.** `AbsApi` sets a per-call `reqwest` timeout from `call_timeout_secs` (default 30). ## Tree and path contract - [x] **G7 — Path parsing is total and reserved-word safe.** Every `/abs/...` shape maps to a variant or `MalformedPath`; the literal `search` segment routes to the search branch and never collides with a (UUID) item id. Empty segments (`/abs//x`) are `MalformedPath`. - [x] **G8 — Root lists only book libraries.** Podcast libraries are filtered (D6). *(test: `root_lists_only_book_libraries`.)* - [x] **G9 — Queueability reflects audio.** A book child is queueable iff `num_audio_files > 0`; an ebook is shown but not queueable/downloadable; an audio-less book node is empty and non-queueable. *(tests: `a_library_lists_search_then_books_with_audio_gating_queueability`, `an_audio_less_book_is_empty_and_not_queueable`.)* - [x] **G10 — Download blessing matches the other providers.** A node is downloadable iff it is queueable or holds tracks; a child is downloadable iff it is queueable. The `search` child is neither. - [x] **G11 — Search terms mirror tidal/youtube/fyyd and are per-library.** `search` is creatable; terms are editable + deletable, deduped, recreated implicitly on stale paths, isolated per library id, and rename/delete are idempotent. *(tests: `search_terms_list_books_and_are_per_library`, `search_terms_rename_and_delete`.)* ## Playback - [x] **G12 — Stream URL is derivable with no API call.** `get_urls_for_track` builds the URL from the path (item id + ino) plus the token, making no network request; the direct and search branches yield the same URL. *(test: `track_stream_url_embeds_the_token_and_path_ids_without_a_call`.)* - [x] **G13 — Track metadata.** `artist` = author, `album` = book title, `title` = file title, `duration` = seconds (`None` when absent), `provider_item_id` = `":"`. *(test: `a_book_lists_its_files_as_tracks`.)* ## Wiring and lifecycle - [x] **G14 — Non-fatal init.** Missing/incomplete `abs.toml` (no `base_url`/`api_key`) disables `/abs` with a warning; it never aborts startup. *(test: `init_requires_base_url_and_api_key`; orchestrator build block mirrors fyyd.)* - [x] **G15 — Orchestrator parity.** `abs` is wired at every dispatch point (`is_track_path`, `get_urls_for_track`, `get_metadata_for_track`, `get_lib_node`, `create`/`rename`/`delete`, `resolve_tracks_into`, root child) and in settings (`ALL_PROVIDERS`, `ProviderToggles`, `all`, `provider_toggles`). - [x] **G16 — Bounded listings.** `items_per_library` and `search_results` cap every listing; truncation to the cap is `log`-ged, not silent. ## Live validation (drift gate) - [x] **G17 — DTOs decode the real server.** `absdy/tests/live.rs` (`#[ignore]`) exercises libraries → items → search → detail against a real server and confirms the shapes. Verified during implementation against the provided test server.