Repository navigation
feat(Session DB): local SQLDelight session storage + upload outbox - #22
Merged
Merged
Conversation
…ests for local data
…, streams, and measurements
The rewrite drops the legacy in-place AveragingService. Measurements are kept at native resolution; averaging happens only on the read side (graph, map, stats). Replaces load-bearing rule #4 (native-interval gate) with "raw measurements are never averaged, down-sampled or deleted in storage", removes averaging from the RecordingEngine sketch, and marks the V2 sparse-interval gotcha as legacy-only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…p key The rewrite has no active_sessions_measurements rolling buffer; the live view is a windowed query on measurements. The dedup key is now PRIMARY KEY (stream_id, time) — the legacy session_id in the index was redundant, and v3 dedups on (stream, timestamp) as well. Updates KMP_ARCHITECTURE §5 and DOMAIN_KNOWLEDGE §4 / load-bearing rule #5. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Records how the v3 upload traps constrain the local outbox: only mobile sessions are uploaded by the phone (downloaded rows must land as uploaded), uploaded means "accepted in a 200 batch" so frames past finished_at must be split out first, clock sync before sending to avoid a poisoned invalid_epoch batch, per-row marking instead of a time cursor, and blind retry on transport errors. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replaces the measurements.uploaded flag design: upload state lives in a key-only pending_uploads table (composite FK to measurements, cascade), written by insertRecorded and cleared by UploadOutbox.acknowledge. Downloaded/followed/explored data goes through insertAll and never enters the outbox, and measurements stays insert-only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Remove the `uploaded` column from the `measurements` table and move upload queue tracking into a dedicated `pending_uploads` outbox table. - Introduce `UploadOutbox` domain interface and `LocalUploadOutbox` implementation for querying pending measurements and acknowledging server syncs. - Add `insertRecorded()` to `MeasurementRepository` to atomically store and enqueue newly recorded measurements, leaving `insertAll()` for non-uploadable imported data. - Remove the `uploaded` column from `Measurement.sq` and property from the `Measurement` domain model. - Register `UploadOutbox` in `DatabaseModule` and add `UploadOutboxTest` host tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the local persistence layer for sessions in
shared(KMP, SQLDelight): schema, domain models, repository interfaces, SQLDelight-backed implementations, Koin wiring, and host tests. Also records the storage decisions in/docs.Schema (
shared/src/commonMain/sqldelight/.../db/)sessions— keyed byuuid;type/statusas enums,tagsasList<String>, times askotlin.time.Instant(epoch millis),measurement_interval_millis, optionalsession_token.measurement_streams— FK →sessionsON DELETE CASCADE; unique(session_uuid, sensor_name)sofindOrCreateis idempotent.measurements—PRIMARY KEY (stream_id, time)+INSERT OR IGNORE. Same dedup key as the backend's v3 rule; replaces legacy(session_id, stream_id, time)index. One table, one write path — no separate active-session buffer table.pending_uploads— upload outbox holding only keys(stream_id, time), composite FK →measurementsON DELETE CASCADE.measurementsstays insert-only.Foreign keys are enabled per connection on both platforms (Android driver
onOpencallback, iOSextendedConfig.foreignKeyConstraints) — otherwise SQLite ignoresON DELETE CASCADE.Domain + repositories
Session,MeasurementStream,Measurement(domain/session).SessionRepository,MeasurementStreamRepository,MeasurementRepository,UploadOutboxinterfaces, withLocal*implementations onDispatchers.IO.MeasurementRepositoryhas two write paths:insertAll— data from elsewhere (downloaded/followed/explored/fixed). Stored only, never enqueued.insertRecorded— data recorded on this phone. Stores and enqueues in one transaction, enqueuing only newly stored rows.UploadOutbox.pending(sessionUuid, limit)returns oldest-first;acknowledgedeletes outbox rows by natural key (correct when BLE sync backfills older timestamps after newer ones were sent).DatabaseModule(Koin).Docs
DOMAIN_KNOWLEDGE.md— averaging is display-only; stored measurements are never averaged/down-sampled/deleted. Dedup key rule updated to(stream_id, time).API_V3.md— outbox rules: only locally recorded data is enqueued;200≠ stored (split onfinished_atbefore sending); sync clock before first batch; track per row, never a time cursor; blind retry is safe.KMP_ARCHITECTURE.md,ble_mobile_app_guide.md— aligned with the above.Open decision (noted in
API_V3.md): how frames past the server-sidefinished_atleave the outbox — boundpendingbyfinished_at, or add astatecolumn topending_uploads.Tests
androidHostTest(JVM SQLite driver added to that source set):SessionRepositoryTest— full-field round-trip, null location,observeAllre-emits on insert,finishsets status + end time together,updateStatusleaves end time alone.MeasurementRepositoryTest—findOrCreateidempotent, dedup on(stream, time)incl. partial-overlap counts, same timestamp across streams, location round-trip,lastTime, session delete cascades to streams + measurements.UploadOutboxTest—insertRecordedstores + enqueues,insertAllnever enqueues, acknowledged duplicate not re-enqueued,pendingoldest-first/limited/scoped per session,acknowledgekeeps the measurement, failing batch is atomic, session delete clears pending uploads.Out of scope
Uploader itself, read-side averaging, UI wiring.
🤖 Generated with Claude Code