# Quality gates — fyyd provider LLM-verified gates for `architecture/fyyd-provider.md`. Automatic tests live in `fyyd/src/lib.rs` (provider logic against a fake `Fyyd`) and `crabidy-server/src/settings.rs` (the provider toggle). ## Correctness / semantics - [x] Tree shape matches D3: `/fyyd` → `search` (creatable) + `hot`; `/fyyd/search/` and `/fyyd/hot` list **podcasts** as children (not tracks); `/fyyd//` lists **episodes** as tracks; `/fyyd///` is the track leaf. Verified by `root_lists_search_and_hot`, `search_terms_list_podcasts_as_children`, `a_podcast_lists_its_episodes_as_tracks`, `hot_lists_featured_podcasts`. - [x] A podcast listing node is not itself queueable; each podcast child is queueable and downloadable (queue/capture a whole podcast). An episode list node is queueable and downloadable. `is_track_path` is true only for episode leaves. Verified by the tests above and `tracks_resolve_streams_and_metadata`. - [x] Search terms are creatable/renamable/deletable exactly like tidal/youtube (in-memory, dedup, implicit recreation on stale paths), and only `/fyyd/search` is creatable. Verified by `search_terms_list_podcasts_as_children`, `search_terms_rename_and_delete`, `foreign_and_malformed_paths_are_rejected`. - [x] Playback resolves an episode to its `enclosure` URL; a track's `artist` is the podcast title and `provider_item_id` is the fyyd episode id. Verified by `tracks_resolve_streams_and_metadata`. - [x] The download blessing is applied centrally (node downloadable when queueable or track-bearing; children mirror queueability), so `W` captures work with no capture-side change. ## Robustness (hard rules) - [x] No panic on any input: malformed/foreign paths, empty create/rename input, a missing enclosure, and every backend failure return a typed `ProviderError`, never a panic. Verified by `foreign_and_malformed_paths_are_rejected`, `a_missing_enclosure_is_an_error_not_a_panic`, `backend_failures_are_typed_never_panics`. - [x] Every external call is bounded by a timeout (`call_timeout_secs`, default 30) set on the `reqwest` client; every listing is capped (`search_results`, `hot_count`, `episodes_per_podcast`). D5. - [x] fyyd responses decode defensively: the `data` envelope is unwrapped, DTO fields use `#[serde(default)]`, entries without a usable id are dropped, and non-positive durations degrade to `None`. Failures are typed `FetchError` → `ProviderError::FetchError`. - [x] No secrets: fyyd's public API uses no credentials, so nothing secret is logged; only public URLs/ids appear in traces. ## Integration / operability - [x] `fyyd` is a registered provider: in `ALL_PROVIDERS`, `ProviderToggles` (`all()` + `provider_toggles()`), and the default `crabidy-server.toml`. Disabling it in the `providers` list drops the `/fyyd` subtree. Covered by the existing settings tests (which iterate `ALL_PROVIDERS`). - [x] Init is non-fatal: a client that fails to build disables `/fyyd` with a warning and leaves every other provider and the server running (D1). A missing `fyyd.toml` is normal (no credentials needed). - [x] The orchestrator routes every `ProviderClient` method for `/fyyd` paths to the fyyd client (owns-check + `fyyd_provider()`), and `get_lib_root` lists `fyyd` only when the client is mounted. No proto change and no new `ProviderCommand` were needed. ## Live validation (deferred, needs network) - [ ] Against the real `api.fyyd.de`: a search returns podcasts, a podcast lists episodes with non-empty `enclosure` URLs, and an enclosure plays through the audio player. If a field name differs from the documented shape, the fix is confined to `fyyd/src/api.rs` DTOs (design risk noted in the architecture doc). This gate cannot run in the offline unit suite.