# Quality gates — jamendo provider Gates for the `/jamendo` provider (architecture/jamendo-provider.md). Automatic tests live inline in `jamendody/src/tests.rs` (unit, over a `FakeJam`) and are named in each gate. LLM gates are read-and-reason checks an implementing agent verifies. ## Automatic tests (jamendody/src/tests.rs) - **T1 `root_has_only_search`** — `get_lib_root` exposes exactly one child, `/jamendo/search` (creatable), and the root is not queueable. (D3) - **T2 path parsing** (`*_parse`, `foreign_and_malformed_paths_reject`) — `/jamendo`, `/jamendo/search`, `/jamendo/search/`, `/jamendo/track/`, `/jamendo/album/` parse to their variants; foreign roots, empty segments, wrong arity, and unknown tags are `MalformedPath`. (D3) - **T3 `is_track_path_only_for_canonical_track`** — true only for `/jamendo/track/`; false for albums, search nodes, root. (D3/D4) - **T4 `search_term_lists_tracks_and_albums`** — a search node carries matching tracks as rows at canonical `/jamendo/track/` paths and matching albums as **queueable** container children at `/jamendo/album/`; the node is queueable and `is_downloadable`. (D3/D4) - **T5 `album_node_lists_tracks`** — `/jamendo/album/` lists the album's tracks as canonical rows; node is queueable + downloadable. (D3) - **T6 `get_urls_for_track_returns_audio`** — `get_urls_for_track` on a track path returns exactly the backend `audio` URL; a non-track path is `MalformedPath`, not a stream. (D4) - **T7 `track_metadata_maps_fields`** — `get_metadata_for_track` maps `name→title`, `artist_name→artist`, `album_name→Album.title`, `duration(secs)→Track.duration` unchanged, `provider_item_id == "track:"`. (D4) - **T8 `create_rename_delete_search_term`** — creating a term adds an editable + deletable child under `/jamendo/search`; empty/whitespace title → `InvalidInput`; rename moves the term; delete removes it. (D3) - **T9 `backend_failures_are_typed`** — a backend whose every call errors makes `get_lib_node` return `ProviderError::FetchError`, never panics. (D2) - **T10 `settings_debug_redacts_client_id`** — `format!("{:?}", settings)` with a `client_id` set does not contain the id and shows ``. (D5) - **T11 `duration_is_seconds_not_ms`** — a track whose backend `duration_secs` is 210 yields `Track.duration == Some(210)` (guards against a ms/seconds regression like the web bug). (D1/D4) ## LLM quality gates (read-and-reason) - **G1 — no panics on external conditions.** No `unwrap`/`expect`/`panic!` on network, decode, config, or path input in `jamendody`. Lock poisoning degrades to empty (`unwrap_or_default`), never panics. (Hard rule) - **G2 — typed error boundary.** Every `JamApi` failure is a `FetchError` variant; the provider maps it to `ProviderError` (`FetchError` / `MalformedPath` / `NotSupported` / `InvalidInput`) and logs the typed cause. No `color-eyre`/`anyhow` leaks across the trait. (Hard rule) - **G3 — client_id is redacted everywhere.** `Settings` and `JamApi` have hand-written `Debug` that redact `client_id`; it never appears in a log line, and a resolved `audio` URL (may carry a signed token) is never logged. (Hard rule: redact secrets) - **G4 — timeouts on every external call.** The `reqwest` client is built with a timeout from `call_timeout_secs`; no unbounded HTTP call exists. (Hard rule) - **G5 — bounded results, no silent caps.** `search_results` (≤ API max 200) and `album_tracks` bound listings; when a cap truncates, it is `log`-ged (`debug`/`warn`), not silent. (Hard rule: no silent caps) - **G6 — non-fatal init.** A missing/empty `client_id` (or unparseable `jamendo.toml`) makes `init` return a typed `Config` error; the orchestrator maps it to `None` so only `/jamendo` is lost, never the server. No blocking network in `build()`. (D1/D5) - **G7 — canonical leaf independence.** Track rows and album children always use `/jamendo/track/` and `/jamendo/album/` regardless of the branch they were reached through, so playback/expansion never depends on browse context. (D3) - **G8 — no proto/wire/TUI/player change.** The feature is a pure path-prefix subtree: no edit to `crabidy.proto`, `ProviderCommand`, the TUI, or `audio-player`. Search-term flows reuse the existing create/rename/delete RPCs. (Assumptions) - **G9 — defensive DTOs.** Wire structs use `#[serde(default)]`; a missing field degrades (empty/`None`), a renamed field is a local `JamApi` fix, never a crash. Ids decode whether numeric or string. (R3) - **G10 — docs.** Public items (`Client`, `Settings`, `PROVIDER_ROOT`, the `Jam` trait and its methods, domain types) carry doc comments stating intent and error behavior. (Preference)