Implementation log #
Concise record of important choices, spike results, deviations and blockers. Newest entries last.
2026-10-01 — Bootstrap #
- Toolchain: JDK 17 (Temurin 17.0.16+8 via mise), Gradle 8.10.2 wrapper, Android SDK
platform 35 + build-tools 34.0.0 installed under
~/android-sdk. Reproducible viascripts/setup-android-sdk.shandscripts/setup-gradle.sh. - Version pins chosen (see
docs/build-environment.md). Notable: Compose BOM 2026.01.00 (compose 1.10.1) is the newest BOM compatible with AGP 8.6.1 + compileSdk 35; BOM 2026.09.00 pins compose 1.12.1 which requires compileSdk 37/AGP 9.1 — rejected. Compose 1.10.x requires compileSdk 35, so compileSdk=35/targetSdk=34 everywhere. - Full Gradle multi-module skeleton created per the spec's module layout;
assembleDebugpasses;libtorrent4jnative lib for armeabi-v7a is packaged in the debug APK (11.7 MB). :core:modeldomain contracts (MediaRef, SourceResolver, ResolvedAnswer/playback targets, state machines, QualityProfile, provider capabilities, primary-account policy) with 13 JVM tests passing.
Hardware gates (status) #
A Google TV Streamer is now reachable through an authorized laptop bridge. The
2026-10-02 read-only inventory records real firmware/ABI/package
evidence. M0 remains incomplete pending DRM and reboot/reconnect acceptance; every M0.5 spike
still lacks the required device outcomes and remains blocked, not skipped. The read-only M0 inventory
script and docs/spikes/<name>.md protocols document prerequisites and measurements; the
spikes still need their isolated harnesses and real-device execution.
Implementation proceeded for everything that does not depend on those results, per the spec's
M0.5 rule ("A spike may block its dependent feature without blocking unrelated milestones").
Follow-up — safe canonical mapping for Letterboxd imports #
- Continued the existing, uncommitted account-client work. Letterboxd matching now requires a unique title-and-year match across movie and series search results, rather than selecting the first same-year hit. Title comparison ignores case and repeated whitespace only.
- Missing dates, ambiguous matches and incomplete search result sets remain unmatched for a future manual-pick UI. Provider failures still propagate instead of returning a successful partial import. No database replacement or UI integration is implemented here.
- Series matches preserve their series flag and canonical
tmdb:tvidentity. TMDB mapping accepts the known endpoint type; recommendations use the seed type, with separate movie and series cache keys even when numeric IDs collide. - Added 11 synthetic-fixture/unit regression tests covering matching, ambiguity, original rank/provenance, provider failure, endpoint typing and recommendation cache separation. These fixtures do not establish live provider compatibility.
- Host verification:
:core:model:test,:core:network:test,:accounts:test,:merge:test,:resolver:test,:app:testDebugUnitTestand:app:assembleDebugpass. Device gates remain blocked; no milestone completion, launcher replacement or engine enablement is claimed.
Follow-up — transactional library persistence #
- Added the initial version-1 Room schema in
core/database, exported toschemas/, with users/Household, accounts, feeds, canonical title/episode metadata, source provenance, local watchlists/progress, normalized audience membership and primary write selections. LibraryStoreprovides domain/Flow boundaries, atomic complete/delta feed updates, non-destructive failure recording, stale-attempt rejection and explicit reauthorization. Sparse metadata updates preserve richer cached fields; recommendation provenance retains every seed. Deltas require a complete baseline and cannot contain contradictory operations.- Foreign keys implement ownership cascades without deleting shared canonical metadata. Household protection and exactly-one progress targets are also enforced by SQLite triggers. Same-provider/same-label accounts are allowed; primary choices are capability- and user-scoped.
- Added 27 Robolectric/Room tests covering actual SQLite constraints, cascade retention, retries, failed/partial syncs, transaction rollback, primary policy, progress, Flow updates, file persistence and unsupported-version failure without destructive recreation.
- Review fixes: successful-attempt retries no longer overwrite later shared metadata changes;
ItemSourcecarries its originating feed ID through merge. Fixed shared-item detection to count distinct users, not multiple source records from one user, with another JVM regression. - Full host regression: 159 tests pass across model, network, database, accounts, merge,
resolver and app;
:app:assembleDebugpasses. No real-device behavior was exercised. - Storage is not yet wired into app startup, provider sync or credentials. Remaining tables,
integration boundaries and migration requirements are documented in
docs/storage.md. M5 and device-dependent milestones remain incomplete; no architecture deviation selected.
Reviewed track integration #
- Landed provider progress/subtitle storage (schema 3, migrations 1→2→3), corruption-preserving startup boundary, app startup/recovery UI, the credential-store boundary, and provider-independent sync coordination after review/fixes and full verification before and after each rebase.
tracks.jsonnow declares the user-approved gate: every Gradletesttask, including debug/release Android host variants, plus debug APK assembly. The broader gate caught a debug-only Compose host registration; fixed test setup without shipping a release test activity or dropping release coverage. Seedocs/track-integration.mdfor deferred nonblocking checks.- Domain/credential/sync boundaries remain separate: live account credential entry, concrete Room/provider sync adapters and durable write retries are not implemented. Stremio remains on its unfinished track. Hardware gates and milestone acceptance remain unclaimed.
- Configured the user-supplied SSH origin; no push was performed during track landing.
M0 — first read-only device inventory #
- Collected real firmware/ABI, package/version/installer, memory/storage and display evidence through the authorized laptop bridge. No device changes, install, reboot or denied-command workaround were attempted. The report and sanitized JSON contain no network endpoints, SSH identities, transport IDs or raw package dumps.
- Measured build
UTTK.260317.003, Android 14/API 34, ARMv7 app ABIs; existing target/native pins remain unchanged. User 0 has 137 installed packages, 136 enabled and one already disabled. - Recorded the actual Apple TV package (
com.apple.atve.androidtv.appletv) as a follow-up to the stale application default; application behavior was not changed in this inventory task. - Generated text-only recovery instructions for the five confirmed spec candidates, without
executing them. DRM quality, Widevine/Integrity, reboot/reconnect and M0.5 outcomes remain
unverified. See
docs/device/2026-10-02/.
Follow-up — measured pin, provider cancellation and Stremio primitives #
- Corrected the fresh
HomeLayoutApple TV pin using the captured package identity. Explicit saved lists are not rewritten; no device/configuration changes were made. - Landed reviewed fractional-rate/cancellation fixes in the shared provider client, with dispatcher-release and interrupted/cancelled no-network tests. Strict sliding-window provider quota orchestration remains separate from the token-bucket mechanism.
- Landed bounded Stremio manifest/catalog/direct-HTTP primitives after iterative review fixes: duplicate decoded-key rejection, last-good retention, safe extras encoding, required headers, finite retry/redirect/body limits, cancellation through permit and body/delivery phases, abort-before-close cleanup, and strict range/framing handling. Prefix evidence stays UNVERIFIED; it does not establish device compatibility, whole-file playability or live-addon protocol compatibility. No production resolver/UI integration or native engine enablement.
- Final integrated host gate passes: 276 distinct cases / 357 debug-release-inclusive test
executions, zero failures/errors/skips, plus debug assembly. Detailed contracts and limitations
are in
docs/stremio-http.md,docs/network-cancellation.md, anddocs/track-integration.md.
Cached library → Home integration #
- Added transactional, credential-free Room snapshots of users/accounts/feeds/items/provenance, with invalidation across all contributing tables and no schema change.
- Wired the actual Home activity to those snapshots through an app repository and
FeedMerge: Everyone/person-plus-Household scopes, stable rows, cached-only inspection, complete scoped provenance, closed stale/error status, audience selection and deleted-user fallback. - Review-driven regressions cover populated cache retention on failure/retry/resubscription, actual Room-to-dialog shared-title privacy, and audience selection/deletion/cancellation races. Publication-time revalidation prevents a late selection result from reviving a deleted user.
- Full integrated host gate passes: 285 distinct cases / 375 variant-inclusive executions, zero failures/errors/skips, plus debug APK assembly. No production demo rows, provider calls, device mutation or playable-answer claims were introduced. Hardware focus remains untested.
- See
docs/cached-home.mdfor viewing semantics and limitations. Live account setup/sync, Continue watching integration, configurable pinned/hide-watched rows, detail/playback and physical-device acceptance remain separate work; this is not complete M5/M6/M7 acceptance.
First beside-stock device smoke test #
- Installed
dev.hzz.streamer.debugversion0.1.0/ code1from source3ddcd79on the authorized Google TV Streamer; ADB confirmed successful installation and package identity. - Operator reported successful initial launch and passed the five-step remote/app-launch,
Home-to-stock, reopen and Settings/Back checklist. No default-HOME or protected-package
changes were performed. See
docs/device/2026-10-04-launcher-smoke.mdfor exact scope. - This is partial M2 hardware evidence with an empty library. It does not establish DRM playback, populated-row focus, gamepad support, reboot/wake, or launcher replacement.
Durable account-wide tracking storage #
- Added opt-in
RoomTrackingSyncStoreover account-wideTrackingStoragetransactions: reserved attempts, success cursors, full/delta title+episode facts, explicit removals, independent authorization/failure status and retry idempotency. - Schema 4 migrates explicitly from 3, preserving older data/exports and safe-opening behavior. Imported episode metadata is retained without treating it as parent metadata or projecting episode history into whole-series rows. Partial playback progress remains a separate API.
- Independent review and regressions cover cancellation/rollback, deletion/recreation and stale completion, reader isolation, migration/reopen throttling and malformed late stages through the actual coordinator while a healthy sibling commits.
- Full gate passed before/after landing: 296 distinct cases / 397 variant executions, zero failures/errors/skips and debug assembly. No APK update or other device mutation.
- Production binding remains off. A parallel audit recorded the current Simkl protocol and
transport gaps in
docs/simkl-adapter-gaps.md; synthetic clients cannot establish complete pulls or safe deltas. Seedocs/tracking-sync-storage.mdfor the storage contract.
Account-free cached search and bounded HTTP #
- Home now has typed cached-library search with deterministic, scoped title/year results, bounded input/results and shared metadata/provenance inspection. Open inspection follows current ownership snapshots; tests cover scope/deletion changes, Back/Home/guide, keyboard controls, focus restoration and lifecycle reset. No online search or Play action is implied.
JsonHttpnow cancels real OkHttp calls on coroutine cancellation and bounds decoded bodies (4 MiB default), including chunked/gzip responses. Rejection cancels before close to avoid hidden draining; callback failures are sanitized and complete the caller. POST bodies are one-shot, with explicit redirect caveats rather than unsafe mutation retries.- Independent review found and fixed tied-provenance ordering, a suspended-caller decoder Error path and a flaky cancellation test. Network tests passed three fresh runs by the writer and three by the reviewer; both tracks passed follow-up review.
- Declared gate passed after rebasing and again on integrated main: 316 distinct cases /
422 variant-inclusive executions, zero failures/errors/skips and debug assembly. No real
accounts, live providers, device changes or production demo data were used. See
docs/cached-search.mdanddocs/json-http.md; physical-TV acceptance remains outstanding.
Persistent audience and provider timestamps #
- Home now exposes Who's watching with explicit Apply/Cancel, persisted multiple-person membership, coherent user/audience snapshots and an atomic conflict-aware replacement. Viewing scope remains independent; choosing one person still supplies its audience default, while Everyone does not reset it. Existing Household membership can be retained/removed, but neither the UI nor a constructed draft can introduce it as a person.
- Actual Room/UI tests cover external changes, deletion/recolouring, rollback, reopen, cancellation before/after commit and modal navigation/lifecycle. No schema change.
- Strict minSdk-23-compatible timestamp parsing replaces substring/calendar normalization and receipt-clock fallbacks in tracking imports. Missing/unsupported required dates abort imports; supported formats are explicitly narrow and do not claim real API compatibility.
- Independent reviews and audience validation fix passed follow-up checks. Declared gate passed
after rebase and on integrated main: 332 distinct cases / 448 variant executions, zero
failures/errors/skips, plus debug assembly. No device or live-account changes. Contracts:
docs/audience-ui.md,docs/provider-time.md. Scrobbling/hide-watched remain unimplemented.
Quarantined web diagnostic harness and firmware system tiles #
- Added an explicit debug-only CIO/Activity diagnostic harness with short-code pairing, bounded session/request state, ordered Start/Stop and independent maximum run lifetime. Reviews exposed transport limits: CIO idle expiry is not autonomous and ambiguous framing can produce extra rejection responses. Network mode is therefore runtime-quarantined; only same-device loopback diagnostics are approved. No production branch/M0.5 pass selected.
- Real host tests distinguish invalid-request state mutation from an independently credentialed valid follow-up, characterize the remaining parser defect without claiming compliance, and enforce quarantine. Debug/release APK checks prove harness exclusion from release.
- Installed the reviewed debug build after authorized laptop reconnect. Operator reported failed Inputs/Notifications tiles. Device resolution identified an available notification action, depending on stock Google TV; no input picker was found. Corrective system navigation now resolves accessible system handlers and shows honest unsupported-screen explanations.
- Corrective source
0e32dccinstalled successfully and Home opened. Operator retest is pending; shell dashboard launch is not ordinary-app or input-switching acceptance. Seedocs/device/2026-10-04-system-tiles.mdanddocs/system-tiles.md. - Final integrated gate: 367 distinct cases / 490 variant-inclusive executions, no failures, errors or skips; debug/release assemblies and APK boundary checks pass. Working hardware baseline, DRM, real tailnet/browser lifecycle and populated-library acceptance remain separate.
TV focus and app-card visual correction #
- Operator reported low-contrast focus without a visible border and oversized capsule app buttons with small top-left icons. Added shared TV controls with a 3dp white focus border, contrasting focused fill/text and modest scale. App cards are 160×148dp rounded rectangles with centered 72dp icons, centered two-line labels and neutral missing-artwork placeholders.
- Independent review and the full gate passed before/after landing: 369 distinct cases / 494 variant executions, zero failures/errors/skips and debug assembly. Host tests cover style contrast, geometry and real D-pad events, not physical-TV visual acceptance.
- Source
511d887installed with data retained; Home launched successfully. Operator visual retest pending. No changes to the unresolved Notifications/Inputs routes or stock defaults. Seedocs/tv-focus.mdfor the visual contract. The operator subsequently judged the visual change good enough for now and requested continued functional work.
Saved progress → Continue watching #
- Corrected reconciliation so older explicit watched state cannot override newer partial
replay progress. Added completed/incomplete
states, exact overflow-safe 90% completion, deterministic ties and lossless per-account provider provenance. - Extended the transactional snapshot to all nine relevant tables and wired a real Home Continue watching row before pinned apps. Progress supplies ownership independently of feed sources; cards retain each visible user's position. Next-up requires cached same-season adjacency and never invents missing episodes or a shared resume offset. Inspection only: no playable answers, playback, scrobbling or live sync were added.
- Review found a persisted-negative-progress Compose crash path. Validation now fails the snapshot read before publication, retaining valid cached rows and a closed retry status. Actual Room tests cover initial failure, hidden/losing invalid records, correction recovery and cancellation; no production repairs or schema changes were introduced.
- Full gate passed before/after landing: 391 distinct cases / 530 variant executions, zero
failures/errors/skips and debug assembly. Source
a4d3aa5installed with-r; Home opened successfully, no demo progress seeded. Populated-row device acceptance remains outstanding. Seedocs/progress-reconciliation.mdanddocs/continue-watching.md.
User-approved Netty evaluation and native startup fix #
- User approved a debug-only Netty evaluation instead of maintaining a CIO fork (D5). Existing Ktor 2.3.12 pin retained; Netty 4.1.111.Final stays out of release. Public hooks implement bounded admitted channels, autonomous phase deadlines, response-aware streaming lifetime and fixed-length-only requests. Security review discovered permissive chunk parsing; all request transfer encodings now reject before that parser path. Network quarantine remains.
- Serial real-socket and Android host tests cover framing/state mutation, cookies/origin/local destination, slow clients, SSE, admission, Stop/cancellation and release isolation. Independent security/build review approved only the loopback evaluation, not production or network access.
- Native deployment exposed a shared protocol regex rejected by Android despite host passes. Reviewed fixed-vocabulary, message-free diagnostics identified PROTOCOL_CONSTRUCT / INITIALIZER / REGEX_SYNTAX. Escaping literal braces fixed it without loosening wire grammar.
- Source
77b366cinstalled: native Netty start, page 200, status 401, single bad-framing 400/EOF, silent connection expiry, short survival after stock Home, and explicit Stop passed. No pairing codes/cookies captured; no phone test. Listener stopped and prior standby restored. - Final gate: 440 distinct cases / 579 variant executions, zero failures/errors/skips, debug/
release builds and APK/dependency boundary checks. See
docs/netty-evaluation.md,docs/netty-android.mdanddocs/device/2026-10-04-netty-loopback.md. Next is a separately reviewed network-enabling change and actual paired-phone/tailnet/standby measurement.
Per-row hide-watched policy over the audience #
- Added
HideWatchedin:merge: aWatchedIndexof completed movie/series states per user from the existing reconciler, andapplyfor the twoHideWatchedPolicyvalues over an audience with Household removed. Completed episodes never mark a series watched; an empty or Household-only audience hides nothing.RowPolicy.defaultForexempts history and defaults every other feed kind to hide-if-all-watched. CachedHomePresenter.rows()applies the row default to each cached Home row using the durable playback audience, while the selected person still scopes sources. Imported progress counts only through an account the same user owns. Cached search asks for unfiltered rows so a watched title is still findable.- Per-row settings are not stored or editable; the older unused
HideWatchedFilterinForYouSeeds.ktis left in place. No provider, network, playback or device changes. Merge and presenter tests cover the policy semantics and the wiring. Seedocs/hide-watched.md.
Encrypted configuration backup format #
- Added
webserver/backup: a passphrase-encrypted file (BackupCodec,BackupFormat.kt) with a fixed 46-byte header of magic, version, Argon2id parameters, salt and nonce as the AES-256-GCM associated data, so any header change fails the tag. Argon2id through BouncyCastle derives the key from the passphrase alone; the local Keystore key is never involved, so a file restores after a reset or on another device. Export defaults are 64 MiB, 3 iterations, parallelism 1 for the 32-bit Streamer; restore checks size, magic, version and parameter caps before deriving a key, so a hostile file cannot make the device allocate. Wrong passphrase and tampering both surface asAUTHENTICATION_FAILED. BackupPayloadis the versioned JSON the file encrypts: users, accounts with their secrets in the clear through aCredentialExporterseam, primary write accounts, subtitle preferences, home layout and quality profile. Cached library data, progress, audience and device profile stay out. Decoding is strict (MALFORMED) and a newerpayloadVersionreportsNEWER_VERSIONbefore any other key is read. An account whose secret cannot be read is exported without one and unauthorized, so restore keeps it and asks for re-entry.RestorePlanner.plandecodes, migrates by version and validates the constraints the database enforces with keys and triggers, reporting oneValidationFault.RestoreFailurewraps the file, payload and validation layers as separate variants.BackupRestorer.restoremakes exactly oneConfigurationWriter.replaceAllcall;RestorePlannerTestproves everyBackupFailure,PayloadFailureandValidationFaultreturns with the writer uncalled, and that decrypted bytes and plan credentials are erased even when the writer throws.- Host-only: no HTTP route, no Room read or write, no
ConfigurationWriterimplementation and no device run. Declared gate passed on the track: 570 distinct cases / 722 variant executions, zero failures/errors/skips, plus debug assembly. Contract and limits are indocs/backup.md.
Subscribed answers from TMDB where-to-watch #
- Added
:resolver:subscribedwithSubscribedBackend: a movie resolves by its TMDB id, a series or episode by the series id (where-to-watch is title-level), and only the configured region'sflatratelist answers.ProviderAppDirectory.MEASUREDmaps the two packages measured on the device (Netflix, Apple TV+); an unmapped provider yields no answer because a guessed package name would produce a dead launch button. TMDB carries no deep links, so a title link exists only through theServiceTitleIdsseam and only for Netflix; the default opens the app's home. Verify keeps installed apps only; a thrown TMDB failure becomes a fixedtmdb_unavailableerror that never echoes the URL or key. - 14 host tests against synthetic MockWebServer fixtures, plus ranking through
SourceResolverImpl. No live TMDB key, no device. Nothing yet assembles a production list of resolver backends, so the backend is not reachable from the app; the household "Streaming subscriptions" setting also does not exist, so an installed app stands in for a subscription.SubscribedBackend.ATTRIBUTIONmust be shown beside any Subscribed answer. Seedocs/subscribed.md.
Stremio addon as an Instant source #
- Added
StremioBackendin:resolver:stremio, the firstResolverBackendover the landed addon primitives:StremioIdsmaps aMediaRefto the addon'stt…id (null means no request),discoverfetches thestreampage throughAddonMetadataHttp.streamsand turns each direct-HTTP candidate into anUNVERIFIEDInstant answer whose release metadata comes from the stream title and whose id is a digest of the request, never the URL or a header. verifyprobes at most the probe's candidate limit in page order and promotes only container prefix evidence, verified for fifteen minutes; rejected and deferred (HLS/DASH) candidates are dropped because no manifest verifier exists and only verified answers are offered.openhands the player the URL and required headers. Page failures are closed codes with fixed text and flip the backend's health until the next complete page.- Supporting changes:
AddonHttpCandidate.releaseNamekeeps the first non-blank oftitleandname, bounded and control-free, forReleaseNameParser;AddonHttpProbe.limitsis readable. No shared model, network or primitive contract changed otherwise. - Host-only: ten MockWebServer tests, no live addon, no device, no Room id lookup and no
production backend list, so the app still offers no Instant answers. Declared gate passed on
the track: 590 distinct cases / 743 variant executions, zero failures/errors/skips, plus
debug assembly. Contract and
limits are in
docs/stremio-backend.md.
Mullvad connection-check probe behind the leak guard #
- Added
MullvadProbein:resolverimplementingLeakGuardPolicy.Probe: one GET ofhttps://am.i.mullvad.net/json, passing only on a200JSON object withmullvad_exit_ipthe booleantrueand a non-blankmullvad_exit_ip_hostname. Seven closed reasons separate a service-shape change (MALFORMED) from a route change (NOT_MULLVAD_EXIT);lastObservationkeeps onlycheckedAt,passedandreason, and the IP, hostname, city, country and body text are parsed on one stack frame and discarded. - One request per check: no redirects, no authenticators,
Connection: close, Retry-After normalised on503/408, and a network interceptor that refuses any second exchange. Connection-failure fallback to the host's next address stays on because it is still one request and a single broken address must not fail every check. The 5 s deadline belongs towithTimeoutOrNull, with OkHttp's call timeout one second later as a backstop, so a cancelled body read is reported asTIMEOUTrather thanNETWORK. Bodies are capped at 64 KiB.:resolvernow depends on:core:network, OkHttp and MockWebServer for tests. - Host-only: 15 MockWebServer cases including the policy pause/resume sequence through the
real probe. No Android glue (connectivity callbacks, transfer pauser, the player's "VPN
check failed" state, wiring
ProviderHttp.client("mullvad")) and no device or Tailscale measurement; the VPN spike stays blocked. Contract and limits are indocs/leak-guard.md.
Configuration backup restore writer and export side #
- Added
app/backup:RoomConfigurationWriteris theConfigurationWriterbehindBackupRestorer. The Keystore and DataStore cannot join a Room transaction, so a restore runs in three stages: every plan secret is stored under a freshCredentialReferencefirst (a failure deletes what was created and stops before the database is touched); one Room transaction reads the old references, runsLibraryStore.clearConfiguration, renames Household, inserts users and accounts under new ids with the fresh references and the plan'sauthorizedflag, and restores selections throughLibraryStore.restorePrimary(which, unlikeselectPrimary, keeps a selection of an unauthorized account); after the commit the old references are deleted best effort and the home layout and quality profile are written. A plan id never survives into a row, so a backup from another device cannot collide with local rows. ConfigurationReaderbuilds theConfigurationSnapshotan export encodes fromLibraryStore.configuration()(one transaction over users, accounts, every primary selection and subtitle preferences) plus the layout and profile read after it.CredentialStoreExporteris theCredentialExporteroverCredentialStore.read: a copy of the secret, null for a lost envelope or a value the store never generated, and a propagatedCredentialStoreExceptionrather than a backup that silently dropped a readable secret.BackupRoundTripTestexports from one Robolectric device and restores into a second, comparing the configurations modulo ids, references and keys, and shows a wrong passphrase and a tampered file leaving a populated target unchanged with the same file still restoring afterwards;RoomConfigurationWriterTestcovers each failure stage on its own.- Host-only: no HTTP route, no passphrase entry and no device run. Declared gate passed on
the track uncached, zero failures, plus debug assembly. Contract and limits are in
docs/backup.md.
Production resolver assembly #
- Added
app/.../resolver/:ResolverAssemblyexposesresolver: StateFlow<SourceResolver>, rebuilt whenever the configuration derived from the accounts changes, with oneStremioBackendperSTREMIO_ADDONaccount (source keyaccount-<id>, addon-scoped HTTP client) and oneSubscribedBackendwhen aTMDBaccount exists. Credential bytes are the raw UTF-8 manifest URL or API key with no envelope; an account whose secret is unreadable is skipped alone.RoomStremioIdsmaps media to the addon id from the cached IMDb id;PackageInstalledAppsasks the package manager.ResolverModuleprovides the assembly in Hilt fromStorageStartup; before storage is ready, or after a failed configuration read, the resolver has no backends. - Health and wanted ids are shared across rebuilds; the answer cache is cleared on each one because cached answers name backends. Unrelated account edits do not rebuild.
SourceResolverImpl.status()now emits for a resolver with no backends;combineover zero flows never emitted.- Host-only: 8 Robolectric tests over a file-backed database, software-key credential store and
MockWebServer. Nothing injects the assembly, so the app still offers no answers; leak guard
stays
UNKNOWN, the device profile is the default, and no credential entry exists for either account type. Declared gate passed on the track with--no-build-cache: 613 distinct cases / 774 variant executions, zero failures/errors/skips, plus debug assembly. Contract and gaps are indocs/resolver-assembly.md.
Production pairing, device tokens and the first API routes #
- Added
webserver/pairing:PairingServicekeeps one QR token (128 bits, ten minutes, single use, issued only when the TV screen asks) and one six-digit code (five minutes; expiry, the fifth wrong attempt and every success replace it), and issues the device token inside its own lock so success and rotation are one step.DeviceTokenshands out 256-bit tokens, stores only their SHA-256 behind thesuspendPairedDeviceStoreseam and compares every stored hash in constant time.RequestOriginclassifies by the numeric local address (tailnet inside 100.64.0.0/10) and names the exact authority a browser would send. - Added
webserver/api:Application.streamerApiservesPOST /api/v1/pair,GET /api/v1/devicesandDELETE /api/v1/devices/{id}with thespec.mderror envelope. Each request passes cookie (401), exact origin on mutations (403BAD_ORIGIN), tailnet-only mutations except pairing (403LAN_READ_ONLY) and JSON content type (415), in that order. No CORS headers; 500 carries no exception text. The local address comes through theRequestOriginResolverseam because the test engine cannot set it; production reads the socket and ignores every header. ApiEventsrecords each answered request, pairing result and revoke with closed values and the answered status only.StreamerApiEventsTestchecks that no token, code, name, address, port, origin or body text reaches a line or a field./pairand unauthenticated refusals record without a rate limit, so a flood can push older events out.- Host-only: no engine, listener, Room store, QR rendering, app wiring or device run; an
IPv4-mapped IPv6 local address is treated as LAN. 48 tests in
:webservercover the core, the routes and the events. Contract and limits are indocs/web-pairing.md.
Usenet discovery through Newznab indexers #
- Filled the empty
:resolver:usenetmodule:NewznabClient(t=movieby IMDb id,t=tvsearchby TVDB id, season and episode;o=json, 1 MiB body and 200-item caps, all-or-nothing parse of the shape variants indexers return),IndexerUsage(per-indexer API and grab counters per UTC day, reserved before each request so a refused one costs nothing) andNzbCache(24 hours undernzb:<sha256>, 8 MiB per NZB). Every failure is a closedNewznabExceptionreason with fixed text, because indexer and NZB URLs carry the API key. UsenetBackendsearches every indexer concurrently, drops camera, telesync and screener releases, and returnsUNVERIFIEDcandidates with apending:placeholder target. Indexer failures fold into the one backend error (indexers_failedwhen several fail).verifychecks only the top 3 byAnswerRanker, reusing cached NZBs, and verifies only on anAvailablearticle sample, expiring with the cached NZB.- The NNTP engine is gated on the M0.5 Usenet spike, so the only sampler is
ArticleSampler.UNAVAILABLE, and with itverifyfetches and verifies nothing;openthrows. No app wiring, Room stores, id lookup or credential entry exists. Host-only: 38 MockWebServer tests with synthetic fixtures, no live indexer and no device. Contract and limits are indocs/newznab.md.