Offline/playback recording robustness (contract-audit follow-ups) #90

Merged
bvandeusen merged 2 commits from dev into main 2026-06-11 14:19:27 -04:00
Owner

Follow-up to the v2026.06.11 history-recording repair. A full Android↔server
connection-point audit cleared the silent-contract bug class and surfaced a
cluster of offline/playback-robustness defects, now fixed.

Fixes

  1. Playback double-count on backgroundPlayEventsReporter close-by-id
    routing + paused-only onStop + in-flight guard; new PLAY_ENDED durable
    mutation kind. Eliminates the duplicate/skip-misclassified row produced on
    every screen-lock.
  2. Replayer poison rowsSENT/DROP/RETRY model; permanent 4xx + corrupt
    payloads dropped, 408/429/5xx/transport retry.
  3. Server idempotencyRecordOfflinePlay dedup on (user, track, started_at); RecordPlayEnded skips a duplicate skip_event on re-close
    (+2 integration tests). Makes at-least-once replay safe.
  4. Like-toggle collapse — supersede older toggles per entity so retries
    can't invert the final like state.
  5. Connectivity-return trigger — drain/sync on NetworkStatusController → Healthy; SyncController.syncSafe gains a single-in-flight mutex.

Commits d2a22e49 + 4ecb1680. CI green on dev head 4ecb1680 (server run 884 ✓,
Android run 885 ✓). Tracked in Scribe task #776.

🤖 Generated with Claude Code

Follow-up to the v2026.06.11 history-recording repair. A full Android↔server connection-point audit cleared the silent-contract bug class and surfaced a cluster of offline/playback-robustness defects, now fixed. ## Fixes 1. **Playback double-count on background** — `PlayEventsReporter` close-by-id routing + paused-only `onStop` + in-flight guard; new `PLAY_ENDED` durable mutation kind. Eliminates the duplicate/skip-misclassified row produced on every screen-lock. 2. **Replayer poison rows** — `SENT/DROP/RETRY` model; permanent 4xx + corrupt payloads dropped, 408/429/5xx/transport retry. 3. **Server idempotency** — `RecordOfflinePlay` dedup on `(user, track, started_at)`; `RecordPlayEnded` skips a duplicate skip_event on re-close (+2 integration tests). Makes at-least-once replay safe. 4. **Like-toggle collapse** — supersede older toggles per entity so retries can't invert the final like state. 5. **Connectivity-return trigger** — drain/sync on `NetworkStatusController → Healthy`; `SyncController.syncSafe` gains a single-in-flight mutex. Commits d2a22e49 + 4ecb1680. CI green on dev head 4ecb1680 (server run 884 ✓, Android run 885 ✓). Tracked in Scribe task #776. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 2 commits 2026-06-11 14:19:16 -04:00
fix: harden offline/playback recording (contract-audit follow-ups)
test-go / test (push) Successful in 42s
android / Build + lint + test (push) Failing after 1m27s
test-go / integration (push) Successful in 4m42s
d2a22e49e3
Audit of every Android↔server connection point (2026-06-11) cleared the
silent-contract class that caused the events `type` bug, but surfaced a
cluster of offline/playback-robustness defects. Fixes:

1. Playback double-count on background. PlayEventsReporter no longer
   enqueues a partial play_offline + leaves the live row open on every
   screen-lock. closeCurrent() now routes by whether the server has an
   open row: close-by-id (durable PLAY_ENDED on failure) when it does,
   offline only when no row exists, and a no-op while a play_started is
   in flight (the server auto-closes that orphan). onStop only durably
   closes a *paused* play — a still-playing one is left to the live path
   under the foreground service. Adds the PLAY_ENDED mutation kind.

2. Replayer poison rows. MutationReplayer now classifies each replay as
   SENT / DROP / RETRY: permanent 4xx (and corrupt payloads) are dropped
   instead of retried forever; 408/429/5xx/transport still retry.

3. Offline-play / close-by-id idempotency (server). RecordOfflinePlay
   dedups on (user, track, started_at); RecordPlayEnded skips a second
   skip_events insert when re-closing an already-ended row. Makes the
   at-least-once replay safe against lost-response duplicates.

4. Like-toggle collapse. Replayer drops like-toggles superseded by a
   later toggle for the same entity, so partial-failure + differential
   retry can't invert the final like state.

5. Connectivity-return trigger. MutationReplayer + SyncController now
   also drain/sync when NetworkStatusController recovers to Healthy, so
   an offline→online transition mid-session doesn't wait for a cold
   start. SyncController.syncSafe gains a single-in-flight mutex.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(android): drop second loop jump in supersededLikeToggleIds (detekt)
android / Build + lint + test (push) Successful in 3m51s
4ecb1680bf
LoopWithTooManyJumpStatements — replace the two continues with a
filtered sequence + a single null guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bvandeusen merged commit 9a57dc4bec into main 2026-06-11 14:19:27 -04:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/minstrel#90