Update Conductarr TV contracts and ratings reads

This commit is contained in:
justinzeus 2026-11-05 22:55:25 +01:00
parent 9dd321f07e
commit f53defad90
13 changed files with 600 additions and 36 deletions

View file

@ -1,10 +1,10 @@
# Conductarr TV client
A UI-free JVM module for the TV operations in the [approved TV spec](https://pages.justinvisser.org/wolfskin/tv-spec), sections 3 and 8. The Android app depends on this module; no screen, navigation, login flow or production service is changed.
A UI-free JVM module for the TV operations in the [approved TV spec](https://pages.justinvisser.org/wolfskin/tv-spec), sections 3 and 8. The Android app supplies the currently selected Jellyfin login through its person-scoped `ConductarrSession`.
## Pinned contract and generator decision
`openapi/openapi.json` is the unmodified OpenAPI 3.1.0 document from Conductarr commit `481492d3a8a0651a89901de548cd511218c0562e`. `openapi/commit.sha` records the source. It was read with `git show <commit>:openapi.json` from the Conductarr clone, not downloaded from a running service.
`openapi/openapi.json` is the unmodified OpenAPI 3.1.0 document from Conductarr commit `7e8f3f0a1c9a88d67b2a9518c1161db84ade531a`. `openapi/commit.sha` records the source. It was read with `git show <commit>:openapi.json` from the Conductarr clone, not downloaded from a running service.
The approved fallback is used: checked-in, maintained kotlinx.serialization data classes and a thin typed TV API, rather than build-time generation.
@ -18,8 +18,8 @@ Thus the generator does **not** handle every construct in this pinned document.
`ContractTest` checks:
- All 20 TV method/path pairs against the pinned paths, including response and mutation body schemas.
- All 68 maintained schemas through their actual serialization descriptors: fields, references, primitive widths, collections, maps, enum wire values and requiredness. Read-only `$schema` metadata is intentionally ignored. Optional scalar properties use nullable Kotlin fields to represent absence; encoding omits them.
- All 22 TV method/path pairs against the pinned paths, including response and mutation body schemas.
- All 70 maintained schemas through their actual serialization descriptors: fields, references, primitive widths, collections, maps, enum wire values and requiredness. Read-only `$schema` metadata is intentionally ignored. Optional scalar properties use nullable Kotlin fields to represent absence; encoding omits them.
- A separately reviewed schema snapshot, `src/test/resources/model-contracts.json`, so changes to formats, limits, defaults or other schema constraints also require explicit review. Models preserve those wire shapes; Conductarr remains responsible for business validation and authority.
When updating the pin, update the models and reviewed schema snapshot together. Do not regenerate the Seerr client into this module or substitute a running server's schema.
@ -28,7 +28,7 @@ When updating the pin, update the models and reviewed schema snapshot together.
Create a `ConductarrClient(baseUrl, jellyfinToken, cache)` for the selected person's Jellyfin session. The caller supplies the token; the client sends exactly `Authorization: MediaBrowser Token="<token>"`. The token must match the pinned contract's 32-hex Jellyfin access-token shape. HTTPS is required except for loopback fake servers. Base URL credentials, query strings and fragments are rejected. A path prefix is preserved.
`TvRoutes` provides typed calls for operator, profiles, feed, dismissals, catalogue options/discovery/title/cast/related/enrichment/seasons, performers/filmography, activity/readiness, requests and library audiences. Request actors cannot be supplied: only the authenticated operator establishes the actor. The request body's requester selects the beneficiary.
`TvRoutes` provides typed calls for operator, profiles, feed, dismissals, catalogue options/discovery/title/cast/related/enrichment/ratings/display/seasons, performers/filmography, activity/readiness, requests and library audiences. Request actors cannot be supplied: only the authenticated operator establishes the actor. The request body's requester selects the beneficiary.
`client.read(call, trigger)` is a cold `Flow<ReadResult<T>>`: collect it once per screen-level read. It emits saved data before starting network work, then a revalidation result when due. `ReadResult.Failure.saved` retains LKG on transport, HTTP and decoding failure. A 401 returns `NeedsSignIn` **without** fallback, invalidates that client's identity-scoped saved data, and prevents further calls on that instance. Create a new instance after sign-in/person switch. No Seerr fallback exists.
@ -49,9 +49,9 @@ Cache keys partition by normalized server URL, credential digest, endpoint, path
| Feed | Server `expires_at`; conditional ETag revalidation |
| Catalogue options | `FOREGROUND` refreshes |
| Discovery pages | 30 minutes per parameterized lane/page |
| Enrichment ratings | 6 hours; 300 entries per movie/TV group; 404 means saved absence, failures never overwrite LKG |
| Title, cast, related, performer, filmography, dismissals, readiness | `OPEN` refreshes |
| Seasons | `OPEN` on season focus; retained per season, 300 entries |
| Ratings-only read | 6 hours; 300 entries per movie/TV group; 404 means saved absence, failures never overwrite LKG |
| Title, cast, related, enrichment, display, performer, filmography, dismissals, readiness | `OPEN` refreshes |
| Seasons | 6 hours for episode scores; `OPEN` refreshes on title-season focus; retained per season, 300 entries |
| Activity | 30 seconds for caller-driven `POLL`, or `FOREGROUND` |
| Title requests and request mutations | No cache |