Remove launch DNS wait and paint saved people immediately #50

Merged
justinzeus merged 2 commits from fm/tv-speed-dns into main 2026-11-07 01:32:20 +01:00
Owner

The prior full suite was not green: four person-switch timeouts occurred on the original head. The review correction uses focused gates only; current results are below. All four names occur in the supplied six-test baseline; no new failing names. Picker input code and its existing regression tests were not changed to hide them.

AI-assisted (fork-internal): implementation and PR description.

Change

  • Share one OkHttp PersistentDns through Jellyfin SDK, Conductarr, artwork/playback and crash reporting. System IPv6/IPv4 ordering is preserved. Successful answers live in memory and a private noBackupFilesDir/wolfskin-dns.json file, capped at 32 persisted hosts. Answers return immediately up to 24 hours old; background revalidation is due after 60 seconds. Failed background lookups back off for 2 seconds without extending the successful timestamp. Negative answers are not cached or persisted; missing, oversized and corrupt files fall back to the system resolver. No new dependency or Android DNS-setting change.
  • On connection-establishment failure before HTTP request headers were sent, do a fresh system lookup and retry once. Never replay a read/response failure or an uncertain mutation. Conductarr retains its no-automatic-replay policy after a request starts. Already-working pooled connections are not forcibly closed; refreshed addresses are used on the next new connection.
  • Start Jellyfin and Conductarr name resolution on parallel IO jobs at process creation. Start Users/Me and System/Info/Public concurrently during session validation rather than serializing them.
  • Restore an unprotected saved login locally before launch validation, allowing the existing saved-people picker to paint names, identity-derived colors and IDs without a network response. Revalidate in the background; identity checks and atomic publication prevent a late launch refresh from replacing a different selected person. PIN/require-login profiles remain protected. Existing validated restoration for background/intent callers remains unchanged.
  • Reuse the shell's existing saved-people path and single-OK person-switch handling. No new main-thread network or file operations. Document the cache, recovery boundary and device check in docker/README.md.

Review correction and current verification

Current pushed head: 052d0b8898c40abd6b357ac5f7802a31d53d4b29. Worktree clean.

Fixed the WebSocket launch-crash review: OkHttp's listener-free derived WebSocket client bypasses HTTP retry bookkeeping rather than throwing for a missing call entry (B1). Also use a name-column-only SQL update and reload the latest saved user so concurrent PIN/preferences/home-settings edits survive revalidation (N2), keep DNS hints in noBackupFilesDir (N5), and contain unexpected resolver exceptions at the refresh/warm-up boundaries (N6).

Correction gate Observed result
Focused app tests 13/13 passed: five real-Room saved-session cases including concurrent PIN/preferences/home-settings edits, the saved-people picker, and all seven person-switch scenarios
Client task 26/26 passed, including the nine requested DNS/WebSocket regressions and 17 existing client cases. The new real-socket WebSocket test opened HTTP 101 through a newBuilder()-derived configured client, received ready, and observed no uncaught dispatcher exception
Single current-head make apk-debug Passed; four APKs passed branding checks with 1,016 string/plural/array resources inspected per APK

All 22 requested review regressions passed. No full make test was run for this correction, per the review gate; the prior full-suite failures below remain tracked, not hidden or claimed fixed. No native SIGABRT, box action, device connection, ADB, installation or optimized build.

Deferred follow-ups, not changed in this correction: revoked-token routing (N1), interim null user-DTO UI (N3), uncapped in-memory DNS hosts (N4), stricter numeric-literal validation (N7), stale-address exposure for cleartext HTTP origins (N8), and bookkeeping for unexecuted/unclosed calls (N9). Intent/deep-link restoration remains the existing blocking validated path.

Original implementation verification

Original tested head: 327f631f0de63312c7c1e165dc3dae02b9db6cc1. The following is historical evidence, not a new full-suite run on the correction.

Check Observed result
Targeted app tests 41/41 passed, including all seven existing person-switch scenarios, saved-session/protection/late-response boundaries, startup timing and the saved-people picker
DNS cache tests 7/7 passed: stale serve/background update, 24-hour boundary, negative-answer recovery/backoff, ordered persistence, corrupt-file recovery, failed first address and refreshed-address HTTP connection
Real local-socket smoke Cached 127.0.0.2 refused; fresh resolution to 127.0.0.1 recovered the profile with one HTTP request and two resolutions. Reopened disk cache, ETag 304 and HTTP 503 saved-data retention also passed
Sole final-head make test App: 711 cases, 707 passed, four known-name picker timeouts. Client: 24/24 passed. Design: 191 passing cases from the successful dependency run; task up-to-date in the final invocation
Sole final-head make apk-debug Passed; four packaged APKs passed branding checks, 1,016 string/plural/array resources inspected per APK

The actual Compose picker was captured and visually inspected while the first household HTTP response was blocked: Justin, Mindel and Shared all displayed with the normal colors and selected-person ring. Proof files are under /root/.treehouse/firstmate-7bab20/2/firstmate/data/tv-speed-dns/. No native SIGABRT occurred; no serial rerun was needed. No ADB, device connection, installation, optimized APK build, live endpoint mutation or Private DNS change.

Exact full-suite failures

All are PersonPickerSessionTest timeouts. Comparison is with the prior episode-navigation and Resume review's six-test list.

Failing test New versus that list?
launchRightThenOneOkKeepsPickerClosed No
fastLeftLeftOkKeepsPickerClosed No
launchLeftThenOneOkKeepsPickerClosed No
avatarLeftThenOneOkKeepsPickerClosed No

The reduced failure count is not a claim that this change fixes the suite-order issue. Person switching still requires the device review below.

On-device review — owner installation only

  1. Keep the box's Private DNS setting unchanged. With saved people present, cold-launch six times, more than 60 seconds apart. Require the full Who's-watching picker within 0.5 seconds every time. This hardware target is not yet measured.
  2. From launch, press Left/Right to another person and OK once. Expect that person's Home, no second picker. Repeat from the avatar picker in both directions.
  3. Launch offline: saved names/colors must still appear without waiting for a network failure. Reconnect and confirm household/session data revalidates. PIN and require-login profiles must still require their existing unlock/sign-in flow.
  4. Compare the launch pair of SDK log lines GET https://jelly.justinvisser.org/Users/Me and GET https://jelly.justinvisser.org/System/Info/Public; ignore later foreground Users/Me refreshes. The spike's slow gaps were 1.508 s and 1.509 s. These requests now overlap and either may log first; compare their timestamp separation and verify the picker does not wait for either response. The minified log tag can change between builds.

Boundaries

The local picker is saved identity metadata, not proof of current server authorization. Revoked credentials remain subject to normal server rejection. A dead IP can still incur the configured connection timeout before fresh-DNS recovery; cached DNS does not bypass TLS validation. Debug APKs are not playback-complete, and the optimized combined build/device timing remain with firstmate and the owner.

> **The prior full suite was not green:** four person-switch timeouts occurred on the original head. The review correction uses focused gates only; current results are below. All four names occur in the supplied six-test baseline; no new failing names. Picker input code and its existing regression tests were not changed to hide them. AI-assisted (fork-internal): implementation and PR description. ## Change - Share one OkHttp `PersistentDns` through Jellyfin SDK, Conductarr, artwork/playback and crash reporting. System IPv6/IPv4 ordering is preserved. Successful answers live in memory and a private `noBackupFilesDir/wolfskin-dns.json` file, capped at 32 persisted hosts. Answers return immediately up to 24 hours old; background revalidation is due after 60 seconds. Failed background lookups back off for 2 seconds without extending the successful timestamp. Negative answers are not cached or persisted; missing, oversized and corrupt files fall back to the system resolver. No new dependency or Android DNS-setting change. - On connection-establishment failure before HTTP request headers were sent, do a fresh system lookup and retry once. Never replay a read/response failure or an uncertain mutation. Conductarr retains its no-automatic-replay policy after a request starts. Already-working pooled connections are not forcibly closed; refreshed addresses are used on the next new connection. - Start Jellyfin and Conductarr name resolution on parallel IO jobs at process creation. Start `Users/Me` and `System/Info/Public` concurrently during session validation rather than serializing them. - Restore an unprotected saved login locally before launch validation, allowing the existing saved-people picker to paint names, identity-derived colors and IDs without a network response. Revalidate in the background; identity checks and atomic publication prevent a late launch refresh from replacing a different selected person. PIN/require-login profiles remain protected. Existing validated restoration for background/intent callers remains unchanged. - Reuse the shell's existing saved-people path and single-OK person-switch handling. No new main-thread network or file operations. Document the cache, recovery boundary and device check in `docker/README.md`. ## Review correction and current verification Current pushed head: `052d0b8898c40abd6b357ac5f7802a31d53d4b29`. Worktree clean. Fixed the [WebSocket launch-crash review](https://git.justinvisser.org/justinzeus/wolfskin/pulls/50#issuecomment-31022): OkHttp's listener-free derived WebSocket client bypasses HTTP retry bookkeeping rather than throwing for a missing call entry (B1). Also use a name-column-only SQL update and reload the latest saved user so concurrent PIN/preferences/home-settings edits survive revalidation (N2), keep DNS hints in `noBackupFilesDir` (N5), and contain unexpected resolver exceptions at the refresh/warm-up boundaries (N6). | Correction gate | Observed result | |---|---| | Focused app tests | 13/13 passed: five real-Room saved-session cases including concurrent PIN/preferences/home-settings edits, the saved-people picker, and all seven person-switch scenarios | | Client task | 26/26 passed, including the nine requested DNS/WebSocket regressions and 17 existing client cases. The new real-socket WebSocket test opened HTTP 101 through a `newBuilder()`-derived configured client, received `ready`, and observed no uncaught dispatcher exception | | Single current-head `make apk-debug` | Passed; four APKs passed branding checks with 1,016 string/plural/array resources inspected per APK | All 22 requested review regressions passed. No full `make test` was run for this correction, per the review gate; the prior full-suite failures below remain tracked, not hidden or claimed fixed. No native SIGABRT, box action, device connection, ADB, installation or optimized build. Deferred follow-ups, not changed in this correction: revoked-token routing (N1), interim null user-DTO UI (N3), uncapped in-memory DNS hosts (N4), stricter numeric-literal validation (N7), stale-address exposure for cleartext HTTP origins (N8), and bookkeeping for unexecuted/unclosed calls (N9). Intent/deep-link restoration remains the existing blocking validated path. ## Original implementation verification Original tested head: `327f631f0de63312c7c1e165dc3dae02b9db6cc1`. The following is historical evidence, not a new full-suite run on the correction. | Check | Observed result | |---|---| | Targeted app tests | 41/41 passed, including all seven existing person-switch scenarios, saved-session/protection/late-response boundaries, startup timing and the saved-people picker | | DNS cache tests | 7/7 passed: stale serve/background update, 24-hour boundary, negative-answer recovery/backoff, ordered persistence, corrupt-file recovery, failed first address and refreshed-address HTTP connection | | Real local-socket smoke | Cached `127.0.0.2` refused; fresh resolution to `127.0.0.1` recovered the profile with **one HTTP request and two resolutions**. Reopened disk cache, ETag 304 and HTTP 503 saved-data retention also passed | | Sole final-head `make test` | App: 711 cases, 707 passed, four known-name picker timeouts. Client: 24/24 passed. Design: 191 passing cases from the successful dependency run; task up-to-date in the final invocation | | Sole final-head `make apk-debug` | Passed; four packaged APKs passed branding checks, 1,016 string/plural/array resources inspected per APK | The actual Compose picker was captured and visually inspected while the first household HTTP response was blocked: Justin, Mindel and Shared all displayed with the normal colors and selected-person ring. Proof files are under `/root/.treehouse/firstmate-7bab20/2/firstmate/data/tv-speed-dns/`. No native SIGABRT occurred; no serial rerun was needed. No ADB, device connection, installation, optimized APK build, live endpoint mutation or Private DNS change. ### Exact full-suite failures All are `PersonPickerSessionTest` timeouts. Comparison is with [the prior episode-navigation and Resume review's six-test list](https://git.justinvisser.org/justinzeus/wolfskin/pulls/48). | Failing test | New versus that list? | |---|---| | `launchRightThenOneOkKeepsPickerClosed` | No | | `fastLeftLeftOkKeepsPickerClosed` | No | | `launchLeftThenOneOkKeepsPickerClosed` | No | | `avatarLeftThenOneOkKeepsPickerClosed` | No | The reduced failure count is not a claim that this change fixes the suite-order issue. Person switching still requires the device review below. ## On-device review — owner installation only 1. Keep the box's Private DNS setting unchanged. With saved people present, cold-launch **six times, more than 60 seconds apart**. Require the full Who's-watching picker within **0.5 seconds every time**. This hardware target is not yet measured. 2. From launch, press Left/Right to another person and OK once. Expect that person's Home, no second picker. Repeat from the avatar picker in both directions. 3. Launch offline: saved names/colors must still appear without waiting for a network failure. Reconnect and confirm household/session data revalidates. PIN and require-login profiles must still require their existing unlock/sign-in flow. 4. Compare the launch pair of SDK log lines `GET https://jelly.justinvisser.org/Users/Me` and `GET https://jelly.justinvisser.org/System/Info/Public`; ignore later foreground `Users/Me` refreshes. The spike's slow gaps were **1.508 s and 1.509 s**. These requests now overlap and either may log first; compare their timestamp separation and verify the picker does not wait for either response. The minified log tag can change between builds. ## Boundaries The local picker is saved identity metadata, not proof of current server authorization. Revoked credentials remain subject to normal server rejection. A dead IP can still incur the configured connection timeout before fresh-DNS recovery; cached DNS does not bypass TLS validation. Debug APKs are not playback-complete, and the optimized combined build/device timing remain with firstmate and the owner.
justinzeus added 1 commit 2026-11-07 00:32:32 +01:00
Cache DNS answers and paint saved people before launch revalidation
Some checks failed
review/security-claude BLOCK: DnsConnectionRetry crashes app on Jellyfin WebSocket (NoSuchElementException)
327f631f0d
Author
Owner

Review (review/security-claude): BLOCK: every Jellyfin WebSocket connection crashes the app

Reviewed SHA: 327f631f0de63312c7c1e165dc3dae02b9db6cc1

Blocking finding

B1. The DNS retry interceptor throws on WebSocket calls. DnsConnectionRetry.intercept starts with attempts.getValue(chain.call()) (DnsConnectionRetry.kt:37). Entries are only created in its EventListener.Factory.create. OkHttp 5.5.1 opens WebSockets on client.newBuilder().eventListener(EventListener.NONE) (RealWebSocket.kt:153-158). That copy keeps the application interceptor but never calls our factory, so the lookup throws NoSuchElementException. AsyncCall.run then rethrows it on the OkHttp Dispatcher thread (RealCall.kt:592-602), and on Android that kills the process.

The Jellyfin SDK 1.7.1 socket goes through exactly this path: OkHttpFactory.createClient = base.newBuilder(), then OkHttpSocketConnection: client.newWebSocket(...). The base is the shared @StandardOkHttpClient (AppModule.kt:97-98, :136-140, :155). ServerEventListener subscribes as soon as current is non-null (ServerEventListener.kt:52-90), so the crash follows every signed-in cold launch, person switch and first sign-in. Playback (PlaybackViewModel.kt:1468) and music (MusicService.kt:437) subscribe too.

Reproduced on a JVM with the real okhttp-jvm-5.5.1.jar and a line-for-line port of the interceptor. A plain HTTP call through the same client works, so the HTTP-only tests can't catch this:

WebSocket onFailure: java.io.IOException: canceled due to java.util.NoSuchElementException: Key okhttp3.internal.connection.RealCall@… is missing in the map.
UNCAUGHT on thread 'OkHttp Dispatcher': java.util.NoSuchElementException: Key okhttp3.internal.connection.RealCall@… is missing in the map.

Fix: val attempt = attempts[chain.call()] ?: return chain.proceed(chain.request()), plus a regression test: a WebSocket through a configured client derived with newBuilder(), against MockWebServer with webSocketUpgrade.

Contract checks that hold

  • Protected profiles stay locked. PIN and require-login rows return ServerOnly before the token is loaded (ServerRepository.kt:128), and the hot-reload and picker guards are unchanged.
  • A late launch refresh can't swap the selected person, and it can't sign anyone out: identity check, UserDto.id check and compareAndSet publish. A failed refresh only logs; saved people are never deleted, and no token is persisted or logged.
  • DNS cache: a failed refresh never extends the saved answer's timestamp. Answers are served for 24 h at most, and at most 32 hosts are saved to disk. A missing, oversized or corrupt file falls back to the system resolver. The cache is thread-safe, and file I/O stays off the main thread.
  • The retry fires only after a connection failure and before request headers start, and at most once. Conductarr keeps retryOnConnectionFailure(false). TLS still verifies the URL host, so a cached IP can't bypass hostname checks.
  • Users/Me and System/Info/Public now run in parallel inside a structured coroutineScope. A Users/Me failure cancels the other call, and cancellation is now rethrown.

Non-blocking notes (full detail in the report)

  1. A revoked token (401) on revalidation leaves the dead session published. Main used to land on the server list instead. Picking yourself in the picker skips validation (WolfskinApp.kt:217). Suggest routing a 401 or 403 to UserList while _current === expected.
  2. revalidateSession writes back the whole user row from the restore-time snapshot (ServerRepository.kt:141; full-row @Update). It can revert appPreferences and homeSettingsSource changes made during the refresh. In a millisecond window between :139 and :141 it could also revert a newly set PIN. Update only the name column.
  3. currentUserDto is null until revalidation or appResume fills it, so the nav drawer is empty and default home rows assume no live TV in that window.
  4. The in-memory DNS host map is uncapped.
  5. The DNS hint file sits in filesDir, which Android backs up (allowBackup="true"). Prefer noBackupFilesDir.
  6. The refresh executor and NetworkStartup catch only UnknownHostException; anything else would be uncaught.
  7. The literal check accepts any string containing :.
  8. For plain-http:// origins only, a 24 h stale IP can send the auth header in cleartext to whatever LAN device now holds that address.
  9. The attempts map leaks entries for calls that are never executed or never closed.
  10. Intent and deep-link launches still restore through the blocking path.

What I read

The full git diff origin/main...HEAD (18 files), plus ServerRepository, MainActivity start/resume, WolfskinApp picker selection, JellyfinServerDao, NavDrawerService, HomeSettingsService, ServerEventListener, AppModule, ConductarrClient, CrashReportSenderFactory, WholphinApplication and the manifest. Upstream sources at the pinned versions: OkHttp parent-5.5.1 RealWebSocket/RealCall, jellyfin-sdk-kotlin v1.7.1 OkHttpFactory/OkHttpSocketConnection. No Gradle or make was run.

## Review (review/security-claude): **BLOCK**: every Jellyfin WebSocket connection crashes the app Reviewed SHA: `327f631f0de63312c7c1e165dc3dae02b9db6cc1` ### Blocking finding **B1. The DNS retry interceptor throws on WebSocket calls.** `DnsConnectionRetry.intercept` starts with `attempts.getValue(chain.call())` (`DnsConnectionRetry.kt:37`). Entries are only created in its `EventListener.Factory.create`. OkHttp 5.5.1 opens WebSockets on `client.newBuilder().eventListener(EventListener.NONE)` (`RealWebSocket.kt:153-158`). That copy keeps the application interceptor but never calls our factory, so the lookup throws `NoSuchElementException`. `AsyncCall.run` then rethrows it on the `OkHttp Dispatcher` thread (`RealCall.kt:592-602`), and on Android that kills the process. The Jellyfin SDK 1.7.1 socket goes through exactly this path: `OkHttpFactory.createClient = base.newBuilder()`, then `OkHttpSocketConnection: client.newWebSocket(...)`. The base is the shared `@StandardOkHttpClient` (`AppModule.kt:97-98`, `:136-140`, `:155`). `ServerEventListener` subscribes as soon as `current` is non-null (`ServerEventListener.kt:52-90`), so the crash follows every signed-in cold launch, person switch and first sign-in. Playback (`PlaybackViewModel.kt:1468`) and music (`MusicService.kt:437`) subscribe too. Reproduced on a JVM with the real `okhttp-jvm-5.5.1.jar` and a line-for-line port of the interceptor. A plain HTTP call through the same client works, so the HTTP-only tests can't catch this: ``` WebSocket onFailure: java.io.IOException: canceled due to java.util.NoSuchElementException: Key okhttp3.internal.connection.RealCall@… is missing in the map. UNCAUGHT on thread 'OkHttp Dispatcher': java.util.NoSuchElementException: Key okhttp3.internal.connection.RealCall@… is missing in the map. ``` Fix: `val attempt = attempts[chain.call()] ?: return chain.proceed(chain.request())`, plus a regression test: a WebSocket through a `configure`d client derived with `newBuilder()`, against `MockWebServer` with `webSocketUpgrade`. ### Contract checks that hold - Protected profiles stay locked. PIN and require-login rows return `ServerOnly` before the token is loaded (`ServerRepository.kt:128`), and the hot-reload and picker guards are unchanged. - A late launch refresh can't swap the selected person, and it can't sign anyone out: identity check, `UserDto.id` check and `compareAndSet` publish. A failed refresh only logs; saved people are never deleted, and no token is persisted or logged. - DNS cache: a failed refresh never extends the saved answer's timestamp. Answers are served for 24 h at most, and at most 32 hosts are saved to disk. A missing, oversized or corrupt file falls back to the system resolver. The cache is thread-safe, and file I/O stays off the main thread. - The retry fires only after a connection failure and before request headers start, and at most once. Conductarr keeps `retryOnConnectionFailure(false)`. TLS still verifies the URL host, so a cached IP can't bypass hostname checks. - `Users/Me` and `System/Info/Public` now run in parallel inside a structured `coroutineScope`. A `Users/Me` failure cancels the other call, and cancellation is now rethrown. ### Non-blocking notes (full detail in the report) 1. A revoked token (401) on revalidation leaves the dead session published. Main used to land on the server list instead. Picking yourself in the picker skips validation (`WolfskinApp.kt:217`). Suggest routing a 401 or 403 to `UserList` while `_current === expected`. 2. `revalidateSession` writes back the whole user row from the restore-time snapshot (`ServerRepository.kt:141`; full-row `@Update`). It can revert `appPreferences` and `homeSettingsSource` changes made during the refresh. In a millisecond window between `:139` and `:141` it could also revert a newly set PIN. Update only the name column. 3. `currentUserDto` is null until revalidation or `appResume` fills it, so the nav drawer is empty and default home rows assume no live TV in that window. 4. The in-memory DNS host map is uncapped. 5. The DNS hint file sits in `filesDir`, which Android backs up (`allowBackup="true"`). Prefer `noBackupFilesDir`. 6. The refresh executor and `NetworkStartup` catch only `UnknownHostException`; anything else would be uncaught. 7. The literal check accepts any string containing `:`. 8. For plain-`http://` origins only, a 24 h stale IP can send the auth header in cleartext to whatever LAN device now holds that address. 9. The `attempts` map leaks entries for calls that are never executed or never closed. 10. Intent and deep-link launches still restore through the blocking path. ### What I read The full `git diff origin/main...HEAD` (18 files), plus `ServerRepository`, `MainActivity` start/resume, `WolfskinApp` picker selection, `JellyfinServerDao`, `NavDrawerService`, `HomeSettingsService`, `ServerEventListener`, `AppModule`, `ConductarrClient`, `CrashReportSenderFactory`, `WholphinApplication` and the manifest. Upstream sources at the pinned versions: OkHttp `parent-5.5.1` `RealWebSocket`/`RealCall`, jellyfin-sdk-kotlin `v1.7.1` `OkHttpFactory`/`OkHttpSocketConnection`. No Gradle or make was run.
justinzeus added 1 commit 2026-11-07 01:23:47 +01:00
Keep DNS retry safe for WebSockets and preserve saved user edits
All checks were successful
review/security-claude Blocking finding fixed on 052d0b88; delta reviewed
052d0b8898
Author
Owner

Delta review of the fix commit 052d0b8898 (read in full: 10 files, +144/-17) against the Opus security review's blocking finding on 327f631f.

B1 (WebSocket crash) is fixed: DnsConnectionRetry.intercept now uses attempts[chain.call()] ?: return chain.proceed(chain.request()), so OkHttp's derived WebSocket client (EventListener.NONE) passes through with no retry. A regression test opens a WebSocket through a configure()d client derived with newBuilder() against MockWebServer.

The cheap non-blocking notes were folded in as asked: N2 revalidation now updates only the name column (new updateUserName DAO query) and re-reads the row, so it can no longer revert preferences or a PIN change made while the refresh was in flight; N5 the DNS file lives under noBackupFilesDir; N6 the refresh task and NetworkStartup.warm catch Exception, not only UnknownHostException. N1, N3, N4 and N7 to N9 stay as follow-ups.

No new blocking finding. Checks: lane ran focused WebSocket/DNS, real-Room saved-session, saved-picker and seven person-switch tests and one make apk-debug on this head; no full suite (known unrelated picker timeouts, separate fix in flight). Forgejo Actions is disabled on this repo.

Delta review of the fix commit 052d0b8898c40abd6b357ac5f7802a31d53d4b29 (read in full: 10 files, +144/-17) against the Opus security review's blocking finding on 327f631f. B1 (WebSocket crash) is fixed: `DnsConnectionRetry.intercept` now uses `attempts[chain.call()] ?: return chain.proceed(chain.request())`, so OkHttp's derived WebSocket client (EventListener.NONE) passes through with no retry. A regression test opens a WebSocket through a `configure()`d client derived with `newBuilder()` against MockWebServer. The cheap non-blocking notes were folded in as asked: N2 revalidation now updates only the `name` column (new `updateUserName` DAO query) and re-reads the row, so it can no longer revert preferences or a PIN change made while the refresh was in flight; N5 the DNS file lives under `noBackupFilesDir`; N6 the refresh task and `NetworkStartup.warm` catch `Exception`, not only `UnknownHostException`. N1, N3, N4 and N7 to N9 stay as follow-ups. No new blocking finding. Checks: lane ran focused WebSocket/DNS, real-Room saved-session, saved-picker and seven person-switch tests and one `make apk-debug` on this head; no full suite (known unrelated picker timeouts, separate fix in flight). Forgejo Actions is disabled on this repo.
justinzeus merged commit fd48426fa7 into main 2026-11-07 01:32:20 +01:00
Sign in to join this conversation.
No reviewers
No labels
exploring
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: justinzeus/wolfskin#50
No description provided.