Add the pinned Conductarr TV client and disk-backed saved data #28
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fm/tv-conductarr-client"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Outcome
Add the UI-free
:conductarr-clientfoundation for the locked TV design. The module supplies typed calls for all 20 TV-used operations, 68 maintained kotlinx.serialization models, identity-scoped disk last-known-good (LKG) storage, and local contract/network verification.Refs: approved TV spec, sections 3 and 8. Implements the pinned-client architecture decision and the Jellyfin token header contract (conductarr#914); does not activate production sign-in.
Pin and generator-versus-fallback decision
Vendor the unmodified OpenAPI 3.1.0 document from Conductarr commit
481492d3a8a0651a89901de548cd511218c0562e, read withgit show <sha>:openapi.jsonfrom the Conductarr clone. The siblingopenapi/commit.sharecords the pin. No schema or endpoint was fetched from a running service.Use the approved maintained-data-class/schema-check fallback. The standalone OpenAPI Generator 7.26.0
kotlinprobe used kotlinx.serialization, coroutines and immutable models. Generation completed and retained nullable arrays, but emittedErrorDetail.valueas@Contextual Any?with no Any adapter. Compiling the actual emitted model and decoding an object-valued error reproducedSerializer for class 'Any' is not found.This is a real wire construct in the pinned schema, so generation does not handle the complete contract. The maintained fallback usesJsonElement?, with regression coverage for arbitrary JSON values.Schema checks inspect the actual serialization descriptors against the pinned schemas (fields, referenced types, primitive widths, lists/maps, enums and requiredness). A separately reviewed schema snapshot also gates changes to formats, bounds, defaults and other constraints. Read-only
$schemametadata is deliberately ignored. The route test independently enumerates every TV method/path pair and checks request/response schemas.Client behavior
Authorization: MediaBrowser Token="<token>"; HTTPS except loopback, bounded connect/read/call timeouts, no redirect or mutation replay.NeedsSignIn, invalidates that identity's saved cache and suppresses stale fallback. Other failures are typed and retain LKG.expires_at, 30-minute discovery, six-hour/300-per-media ratings including cached 404 absence, per-season copies, caller-driven 30-second activity polling, and uncached request receipts.Existing-file edits
Exactly two existing files changed:
settings.gradle.kts: include:conductarr-client.app/build.gradle.kts: oneimplementation(project(":conductarr-client"))line to prove app integration.No design-system, shell,
MainContent.kt, navigation or UI source changed. New module documentation explains the fallback, public API, identity/storage constraints and refresh events.Verification
Rebased onto current
origin/mainbefore the final gates. Both gates ran once, throughdocker/gradle.shserialization:make test: PASS, 6m40s; 550 app tests + 17 module tests, zero failures/errors/skips.live profile Justin -> disk reopen -> ETag 304 -> HTTP 503 retains Justin; requests=3using a real loopback MockWebServer socket.make apk-debug: PASS, 3m30s; full app debug assembly succeeds with the module dependency.Additional local preflight: compile the generator failure reproduction and the fallback with the pinned Kotlin compiler; 17 standalone JUnit tests pass. Tests include 401, 304 reuse across disk reopen, network/HTTP/invalid-JSON LKG recovery, feed expiry, identity isolation, request receipt decoding/no replay, capacity boundaries, corrupt-cache recovery and credential-safe diagnostics. No live Conductarr/Jellyfin calls.
Existing build warnings include optional native decoders absent (MPV stub selected), configuration-time classpath resolution and Gradle deprecations. No check failed. The Kotlin language server exited during reference lookup; scoped text reference inspection was used instead.
Measured size
These are absolute artifact sizes, not a claimed delta. The vendored spec is 474,825 bytes and is a test/build contract, not a runtime resource. Local proof/logs are under firstmate's
data/tv-conductarr-client/task directory; no scratch scaffolding is committed.Deliberately not done
No UI or shell integration, sign-in flow, production activation, live service calls, ratings-only API migration, backend API changes, ADB/device/install work, signed release, version bump, tag, release asset or merge. Screen code still needs to supply the selected person's token, private backup-excluded cache directory and lifecycle refresh events. Firstmate arranges cross-family review and owns the merge decision.
Cross-family review (Claude Sonnet): CLEAN, no blocking findings. Reviewed head
adbfe5d0e7184b56a352cba9669efbdf97ccfe35.Checks:
ErrorDetail.valueclaim holds. The pinned schema has notypethere, and the lane's recorded generator output is@Contextual Any?with a recordedSerializer for class 'Any' is not foundfailure. It is the onlyAnyin 201 generated models, and sits in an error type the client never decodes. A type-mapping workaround was not tried, but spec 3.4 allows the fallback.docker/gradle.sh debug :conductarr-client:test) on a scratch copy with three mutations: a route path renamed in the pinned spec, a model field renamed, and a schemamaxLengthchanged. Exactly those three tests failed (route check, field check, constraint snapshot) and the other 14 passed.git show 481492d3a8a0651a89901de548cd511218c0562e:openapi.jsonis byte-identical to the vendored file (sha256f2c00be3...bb33, 474,825 bytes). That commit is the tip of Conductarrmain.ErrorModel/ErrorDetail. Missing: the spec section 8 "Display art" routeGET /catalogue/titles/{tmdb}/{type}/display(medium follow-up). Extra: none.Authorization: MediaBrowser Token="<token>"and matches the Conductarr server. The token appears only in the header and in the digest that names the cache scope: never in URLs, keys, file names or contents, logs or errors. 401 maps to NeedsSignIn with no stale fallback and clears that scope. Other failures are typed and keep the LKG. Cache paths aredigest(base+token)-scoped, so no cross-person hit. Writes are fsynced temp plus atomic rename, corrupt records become misses, and error bodies never write.settings.gradle.kts+1,app/build.gradle.kts+1). No UI, shell, navigation or design files, no version, signing or release change. SPDX GPL headers are on every new file. The only new resolved artifact is test-onlymockwebserver3.get-cockpit-operator, so the other 19 routes return 403 until the backend widens its allowlist.Full report: firstmate
data/review-wolfskin-28/report.md. Recommendation: merge.