Content is cached in SQLite plus the file system from one GET /course/:courseId/offline-manifest call.
Writes go the other way: completion and playback position queue locally with an idempotency key and replay through
POST /course/:courseId/sync. Completion is monotonic, so a retry can never corrupt a record — which is
exactly why offline exercises are deferred to v2.