AllowedTags and BlockUnratedItems are modelled nowhere #129

Closed
opened 2026-08-14 01:46:07 +02:00 by Claude · 1 comment
Collaborator

Two halves of upstream's visibility rule that this port does not implement at all — consistently absent from both listings and by-id reads, so nothing is inconsistent, and nothing is enforced either.

  • AllowedTags is the other half of IsVisibleViaTags. BlockedTags is now honoured (#111); an account restricted to an allowlist of tags is not.
  • BlockUnratedItems is the branch of IsParentalAllowed that decides what happens to an item carrying no rating at all, per item type. An operator who has blocked unrated items is currently shown every unrated item.

The second is the one that bites in a real library: InheritedParentalRatingValue is only as good as the metadata, and a scan of files with no NFO and no fetcher produces rows with no rating. An account limited to PG sees all of them today.

Where to start

BaseItem.IsParentalAllowed and IsVisibleViaTags in upstream/, and the User policy fields they read. Here: jellyfin_core::visibility::Viewer is the type that already carries the rating limit and the blocked tags, and jellyfin-db's within_the_rating / untagged are where the SQL goes.

BlockUnratedItems is per item type, not a single flag — check what upstream does for a folder and for a series whose episodes differ, because the answer decides whether this is a column comparison or a walk.

How to know it is done

An account with an allowlist and one with unrated items blocked answer the same as upstream on both a listing and a by-id read, measured on a pair with real accounts.

House rules

Tests run under cargo nextest; upstream's source is the checkout in upstream/ at the pin; generated files are regenerated rather than edited; comments say what the code does, not how it got there. Anything needing a write to the reference wants its own container rather than the shared one — #111 did that and it worked: the pinned image over a sqlite3 .backup copy of reference/var/config/data/jellyfin.db, on ports of its own.

Two halves of upstream's visibility rule that this port does not implement at all — consistently absent from both listings and by-id reads, so nothing is inconsistent, and nothing is enforced either. - **`AllowedTags`** is the other half of `IsVisibleViaTags`. `BlockedTags` is now honoured (#111); an account restricted to an allowlist of tags is not. - **`BlockUnratedItems`** is the branch of `IsParentalAllowed` that decides what happens to an item carrying **no** rating at all, per item type. An operator who has blocked unrated items is currently shown every unrated item. The second is the one that bites in a real library: `InheritedParentalRatingValue` is only as good as the metadata, and a scan of files with no NFO and no fetcher produces rows with no rating. An account limited to PG sees all of them today. ## Where to start `BaseItem.IsParentalAllowed` and `IsVisibleViaTags` in `upstream/`, and the `User` policy fields they read. Here: `jellyfin_core::visibility::Viewer` is the type that already carries the rating limit and the blocked tags, and `jellyfin-db`'s `within_the_rating` / `untagged` are where the SQL goes. `BlockUnratedItems` is per **item type**, not a single flag — check what upstream does for a folder and for a series whose episodes differ, because the answer decides whether this is a column comparison or a walk. ## How to know it is done An account with an allowlist and one with unrated items blocked answer the same as upstream on both a listing and a by-id read, measured on a pair with real accounts. ### House rules Tests run under `cargo nextest`; upstream's source is the checkout in `upstream/` **at the pin**; generated files are regenerated rather than edited; comments say what the code does, not how it got there. Anything needing a write to the reference wants its own container rather than the shared one — `#111` did that and it worked: the pinned image over a `sqlite3 .backup` copy of `reference/var/config/data/jellyfin.db`, on ports of its own.
Author
Collaborator

Done, on main, with #128 — see the full write-up there.

AllowedTags is the other half of IsVisibleViaTags and is modelled now, with upstream's ordering kept: blocked tags are tested first, then a row whose first parent is a UserRootFolder, AggregateFolder or UserView is exempt, then the allowlist applies. The exemption cannot leak into the blocked half.

BlockUnratedItems is per item kind, and which kinds answer for themselves is the rule: the default returns false for a folder and an IItemByName; Series, MusicAlbum and BoxSet override it; and Season explicitly returns false, so a season is refused through the ancestor chain rather than by its own bucket. Measured against an account blocking all nine kinds — every season, episode and series 404, the music album still 200 because it answers only for the Music bucket, the artist, the year and the library folders still 200. Upstream exactly.

This was untestable until the fixture grew an account for it. Every row in the default snapshot is unrated, so a rating cap refuses nothing and a blocked tag matches nothing there — unrated / unrated with BlockUnratedItems = ["Movie", "Series"] (2dec147) is the one policy that bites, and it takes the account from the administrator's 19 items to 7. visibility.json carries fifteen entries against it, including the three that must stay 200 — an album, an artist and a year by id — which are the over-blocking guard.

An account blocking all nine kinds went from 25 disagreements to 3; one holding an allowlist from 22 to 1.

Done, on `main`, with #128 — see the full write-up there. **`AllowedTags`** is the other half of `IsVisibleViaTags` and is modelled now, with upstream's ordering kept: blocked tags are tested first, then a row whose first parent is a `UserRootFolder`, `AggregateFolder` or `UserView` is exempt, then the allowlist applies. The exemption cannot leak into the blocked half. **`BlockUnratedItems`** is per item kind, and which kinds answer for themselves is the rule: the default returns false for a folder and an `IItemByName`; `Series`, `MusicAlbum` and `BoxSet` override it; and **`Season` explicitly returns false**, so a season is refused through the ancestor chain rather than by its own bucket. Measured against an account blocking all nine kinds — every season, episode and series 404, the music album still 200 because it answers only for the `Music` bucket, the artist, the year and the library folders still 200. Upstream exactly. This was untestable until the fixture grew an account for it. Every row in the `default` snapshot is unrated, so a rating cap refuses nothing and a blocked tag matches nothing there — `unrated` / `unrated` with `BlockUnratedItems = ["Movie", "Series"]` (`2dec147`) is the one policy that bites, and it takes the account from the administrator's 19 items to 7. `visibility.json` carries fifteen entries against it, including the three that must stay **200** — an album, an artist and a year by id — which are the over-blocking guard. An account blocking all nine kinds went from 25 disagreements to 3; one holding an allowlist from 22 to 1.
Sign in to join this conversation.
No description provided.