MotorSignal-App/docs/archive/2026-09/audits/sync-architecture-gap-audit.md

7.5 KiB

MotorSignal — Current Architecture → Target Architecture Gap Audit

Date: September 1, 2026
Scope: SyncOutbox, SyncModels, SyncProvider, R2UploadService, Firebase sync tests, Phase 2 discovery/implementation evidence, Firestore rules, account/auth integration.

Executive finding

The repository is already substantially aligned with the Phase 2 offline-first design. The correct action is hardening and integration, not replacing the sync architecture.

The authoritative data path remains:

Sensors → local Drift Schema 5 → durable sync outbox → provider → Firestore metadata + R2 telemetry bundle

Cloud availability must never gate recording or local ride persistence.

Current → Target gap list

Area Current Target Gap Action
Local ownership Drift Schema 5 remains authoritative Same None Preserve
Outbox persistence SharedPreferences JSON manifest Crash-resilient durable outbox Interrupted UPLOADING could survive forever; concurrent saves could race Implemented: requeue in-flight items on load and serialize manifest writes
Outbox capacity Fixed 500 item limit Bounded without data loss Oldest items could previously be silently discarded Implemented: evict terminal items only; refuse to discard pending work
Outbox identity Local ride + provider composite Stable idempotency identity Legacy key format was less explicit Implemented: providerId:localRideId plus explicit idempotencyKey
Sync model parsing JSON parsing was permissive Defensive persisted-state parsing Invalid IDs/dates could poison queue Implemented: validate identity, timestamps and attempt count
Provider abstraction Upload-only contract in active code Provider-neutral sync lifecycle Download/header/delete are future capabilities, not yet required by current consumer flow Deferred: add when multi-device restore is implemented; do not fake unused APIs
SyncManager Central queue/retry/dispatch exists Same + auth/network recovery Auth-required work did not automatically resume after login Implemented: retryAuthRequired() and auth-state listener
Crash recovery Upload state persisted Restart must resume safely UPLOADING had no restart recovery Implemented
Firebase provider Firestore + R2 upload implemented Same R2 happens before Firestore metadata; retry can repeat upload Accepted for now: deterministic ride/object identity makes retries idempotent; Firestore remains final metadata confirmation
R2 gateway Worker + Firebase ID token Authenticated UID-scoped object storage Client accepted unsafe ride IDs and unverified worker object keys Implemented: token/empty bundle/path validation and response object-key validation
R2 HTTP handling 401/403 and generic retry Explicit transient/permanent classes 408/429/5xx need SyncManager retry semantics Implemented: transient responses fall through to retryable result
R2 response verification Successful non-JSON response could be treated as success Verify cloud acknowledgement False-positive success possible Implemented: successful response must contain a valid user-scoped object key
SHA-256 Client computes and sends SHA-256 Worker authoritative verification Client-only validation would duplicate trust logic and break existing mocks Implemented: require non-empty hash; worker remains authoritative verifier
Firebase rules User profile + rides + ride descendants Explicit ownership for current/future user-owned collections No explicit motorcycles/trips/friends/events coverage; recursive ride wildcard was broader than necessary Implemented: explicit user-owned collections and deny-by-default fallback
Auth integration Firebase auth abstraction and Riverpod state already exist Auth should drive cloud identity, not recording Auth-required sync did not automatically rearm Implemented: SyncManager listens for authenticated transitions
Account UI Auth foundation exists Account should expose cloud/sync state and profile lifecycle Full account/profile/cloud UX remains a product/UI phase Deferred: no speculative UI changes in stabilization
Firebase sync tests Strong Phase 2B/2D matrix already exists Regression coverage for restart/corruption/security Missing explicit outbox/R2 hardening cases Implemented: new sync_architecture_hardening_test.dart
Phase reports Existing Phase 2 discovery provides architecture baseline; exact MOTORSIGNAL_PHASE_2B/2D/2E_IMPLEMENTATION_REPORT.md filenames were not present at the audited branch paths Implementation evidence should match code Documentation naming/status is not authoritative enough to use as a gate Implemented: this audit records code as source of truth

Intentionally not implemented

  1. No Drift Schema 5 migration. The current outbox design deliberately stays outside the frozen telemetry database.
  2. No Firestore per-sample telemetry writes. High-frequency data remains bundled into R2.
  3. No speculative multi-device download/delete API. Those operations need the final restore/conflict model before being exposed in the core contract.
  4. No billing/Pro gating in the sync engine. Entitlement must remain separate from identity and storage ownership.
  5. No broad Firestore public/group-write rules. Social/group collections require explicit membership and field-level authorization before enabling writes.
  6. No account UI redesign. The auth layer is already decoupled; the remaining work is UX and cloud-status presentation, not identity plumbing.

Resulting target architecture

                 ┌─────────────────────────────┐
                 │ Account / Auth / Profile UI │
                 └──────────────┬──────────────┘
                                │ AuthState
                                ▼
Sensors → Drift Schema 5 → SyncOutbox → SyncManager
                                │
                    ┌───────────┴───────────┐
                    ▼                       ▼
             Firebase Provider         GitHub Provider
                    │
             ┌──────┴──────┐
             ▼             ▼
        Firestore          R2 Worker
        metadata           telemetry.zip
             │             │
             └──────┬──────┘
                    ▼
          durable cloud backup

Operational guarantees after this audit

  • Riding remains fully offline/local-first.
  • A process death during upload does not strand a ride in UPLOADING.
  • Concurrent outbox mutations are serialized.
  • Pending rides are never silently discarded to satisfy capacity.
  • Authentication recovery automatically resumes cloud work after login.
  • R2 object paths cannot be supplied through path traversal-style ride IDs.
  • A nominal HTTP success without a valid worker object key is not accepted as sync success.
  • Firestore access is UID-scoped and deny-by-default outside explicitly supported collections.

Remaining next-phase work

The next cloud milestone is restore/multi-device synchronization, followed by profile/motorcycle cloud documents, conflict handling, explicit account deletion/export flows, and then entitlement-aware cloud limits. Those should be implemented against this hardened foundation rather than replacing it.