5.5 KiB
5.5 KiB
Quality gates — RSS provider
Criteria an implementation of architecture/rss-provider.md must satisfy.
Automated coverage lives in rssdy/src/tests.rs (unit, over a fake
Feeds), plus the settings/toggle tests in crabidy-server.
Secrets (hard rule — highest priority)
- G1 — A feed URL never reaches a log, an error, or a path.
FeedEntry,Settings,Client, andFeedFetcherDebugoutput redact urls (manual impls, not derived).FetchErrorvariants carry status/parse context only. Nodebug!/warn!/error!call takes a feed or enclosure URL. (tests:settings_debug_redacts_urls,feed_entry_debug_redacts_the_url.) - G2 — No path contains a URL. Subscription paths are
/rss/<slug>, episode paths/rss/<slug>/<16 hex>. A tokened URL therefore cannot leak through the UI, the queue,current, or a bookmark toml. (tests:episode_paths_carry_only_slug_and_key.)
Freshness — the point of the feature
- G3 — Listing a subscription always fetches. Two listings of the
same node cause two fetches, and the second reflects a feed that gained
an episode in between. (test:
every_listing_refetches_the_feed.) - G4 — The memo is written by listings and read only by track
lookups. After one listing, resolving every episode of that feed
costs no further fetches; a track lookup with a cold memo fetches
once. (tests:
track_lookups_reuse_the_listing_fetch,a_cold_memo_fetches_once.) - G5 — The memo is bounded to
MEMO_CAPACITYfeeds, evicting the oldest, so subscribing to 100 feeds cannot grow it without limit. (test:the_memo_evicts_beyond_its_capacity.) - G6 —
/rssis inMUTABLE_ROOTSin both clients (cbd-tui/src/rpc.rs,cbd-web/src/state.rs), or the client cache defeats G3. (tests: the existing cacheability tests, extended.)
Identity and paths
- G7 — An episode key is stable across restarts and builds:
blake3(guid)[..16], and the guid falls back to the enclosure URL only when the feed omits one. (tests:episode_keys_are_stable_and_short,a_feed_without_guids_keys_on_the_enclosure_url.) - G8 — Slugs are derived, bounded, and de-duplicated. Lowercased,
non-alphanumerics folded to single dashes, trimmed, length-capped; an
empty or all-punctuation name yields
feed; two subscriptions that slug alike get-2,-3. (tests:slugify_folds_and_bounds,duplicate_slugs_get_a_suffix.) - G9 —
provider_item_idis the guid, so the content store de-duplicates captures of the same episode across visits and feeds. (test:tracks_carry_the_guid_as_provider_item_id.) - G10 — Foreign and malformed paths are typed errors, never
panics: an unknown slug, a wrong-provider path, a bad episode key, and
a path with too many segments all yield
MalformedPath(orNotSupportedfor edits). (test:foreign_and_malformed_paths_reject.)
Subscriptions (%, e, d)
- G11 —
%on/rsstakes a URL, fetches it once, and names the subscription from the feed's own title, de-duplicating the slug. The new node is returned. (test:subscribing_names_from_the_feed_title.) - G12 —
%with a non-URL or an unfetchable URL fails with a typed error and adds nothing. (test:subscribing_rejects_bad_input.) - G13 —
erenames and the path follows the new slug;dremoves the subscription and no audio. Both return what a client should display next. (tests:renaming_moves_the_slug,unsubscribing_removes_only_the_entry.) - G14 — Every subscription change round-trips through
rss.toml—settings()reserializes the live list so the server's write-back persists it, and reloading yields the same subscriptions. (test:subscription_changes_round_trip_through_toml.)
Robustness (hard rules)
- G15 — No panics on feed data. A missing enclosure, empty title,
unparseable date, absent duration, or an entry with no guid is skipped
or degraded — never a panic and never a failed listing. Only an
unfetchable/unparseable feed errors, and it fails that node only.
(tests:
malformed_entries_are_skipped,backend_failures_are_typed.) - G16 — Bounded network use. Every request carries
call_timeout_secs; a body overmax_feed_bytesis rejected without being buffered whole; listings are capped atepisodes_per_feed. - G17 — Newest first, by publication date descending where dates
parse, feed order preserved otherwise (stable sort). (test:
episodes_are_newest_first.) - G18 — A provider failure never takes the server down. A feed
that cannot be fetched fails its own node;
initwith no feeds still mounts an empty, creatable/rss. (test:no_feeds_still_mounts.)
Wiring
- G19 — Behind the
rsscargo feature, default on:dep:rssdy, aBUILT_IN_PROVIDERSentry, anALL_PROVIDERSname, aProviderTogglesfield, a mount registration, and acheck-featuresrow. The provider-less and each-provider-alone builds stay clean. - G20 — Documented. A
docs/src/providers/rss.mdpage (tree, subscribing, premium feeds and what "not cached" means, every config option), anrssdy/README.md, links from the providers index,SUMMARY.md,config.md, and the root README's tree and config table. - G21 — Clippy clean under
-D warningsfor the workspace, fmt clean, and notodo!()from the stub stage left.