# MotorSignal — Phase 2 Cloud Sync & Data Ownership Architecture Discovery **Document Version**: 2.0.0-DISCOVERY **Date**: August 31, 2026 **Status**: ARCHITECTURAL SPECIFICATION & DESIGN REVIEW **Baseline**: Commit `00819ec` (Tag: `pre-feature-development-baseline`), Flutter 3.44.6, Dart 3.12.2, Drift Schema 5, Riverpod 2.6.1 --- ## 1. Executive Summary MotorSignal is an offline-first motorcycle telemetry acquisition, ride-recording, dynamic analysis, and forensic logging platform. Its core telemetry subsystem operates under an absolute **architectural freeze** (authoritative 20 Hz acquisition cadence, complementary sensor fusion, 3.0 MPH kickstand isolation gate, 3-second crash-durability batch writes, and Drift Schema 5 persistence). Phase 1 established the foundation for user identity abstraction (`AuthUser`, `AuthState`, `AuthService`, `FirebaseAuthService`, Riverpod providers, and dual-theme `AccountScreen`), ensuring that user authentication is 100% optional and operates without any coupling to the high-frequency telemetry pipeline. This Phase 2 Discovery outlines the architectural blueprint for introducing **cloud synchronization, multi-device backup, and user data ownership** while strictly adhering to the fundamental principle: $$\text{Authoritative Pipeline:}\quad \text{Sensors (20 Hz)} \longrightarrow \text{Local SQLite (Schema 5)} \longrightarrow \text{Sync Outbox} \longrightarrow \text{Cloud}$$ Cloud availability is never a prerequisite for riding. Unauthenticated, anonymous, and offline rides remain primary first-class citizens. Raw 20 Hz sub-second telemetry traces ($18,\!000+$ samples/hour) are separated from relational summary documents to maintain minimal Firestore costs, avoid document size limits ($1\text{ MB}$), and prevent UI/telemetry thread stuttering. --- ## 2. Current Cloud/Sync Architecture The repository currently exhibits two distinct synchronization and identity mechanisms: ``` ┌────────────────────────────────────────────────────────────────────────┐ │ PRESENTATION & SETTINGS │ │ AccountScreen | EvolutionSettingsView | SettingsScreen | Router │ └───────────────────┬────────────────────────────────────┬───────────────┘ │ (AuthNotifier) │ (GitHubSyncService) ┌───────────────────▼─────────────────┐ ┌────────────────▼───────────────┐ │ PHASE 1 AUTH SUBSYSTEM │ │ EXISTING GITHUB SYNC │ │ AuthUser | AuthState | AuthService │ │ GitHubAuthService (PAT Storage)│ │ FirebaseAuthService (Optional Init)│ │ GitHubSyncService (Contents API│ └─────────────────────────────────────┘ └────────────────▲───────────────┘ │ triggers upload ┌────────────────────────────────────────────────────────┴───────────────┐ │ AUTHORITATIVE LOCAL APPLICATION │ │ RideStateMachine ────(finishRide)────► AppDatabase (Drift Schema 5) │ │ ▲ │ │ │ │ (20 Hz Stream) ▼ │ │ TelemetryService (Frozen) FieldTestExportService │ └────────────────────────────────────────────────────────────────────────┘ ``` 1. **GitHub Sync (`GitHubSyncService`)**: * Uses Personal Access Tokens (PAT) stored in `SharedPreferences`. * Directly invoked by `RideStateMachine.finishRide()` upon session termination. * Uses `FieldTestExportService` and `ExportService` to generate 5 text files (README, JSON, CSVs, GPX) and commits them sequentially via the GitHub Contents API to `bponders12-cell/ride_logs`. * Tracks upload state via `SharedPreferences` flags (`github_uploaded_ride_$rideId`). 2. **Phase 1 Authentication (`AuthService`)**: * Abstraction layer wrapping Firebase Authentication with Google Sign-In and Email/Password. * Manages identity lifecycle completely isolated from `RideStateMachine` and `TelemetryService`. * Unconfigured/offline mode is automatically handled without app crashes or network blocking. --- ## 3. Existing GitHub Sync Analysis ### Current Implementation Characteristics * **Coupling**: `RideStateMachine` directly reads `githubSyncServiceProvider` in `finishRide()`. This creates direct coupling between state machine lifecycle and an external network service. * **Network & Retry Mechanism**: Lacks exponential backoff, persistent outbox queueing, or network reachability listeners; if network fails during sequential PUT calls, the ride enters a `failed` state and requires manual user retry or app restart. * **Payload Footprint**: Uploads individual uncompressed text files directly to a git repository, generating 5 separate HTTP PUT requests per ride session. ### Recommendation * **Transition Strategy**: Refactor GitHub sync from a standalone hardcoded service into a concrete implementation of a unified `SyncProvider` (`GitHubSyncProvider`), managed by a central, provider-agnostic `SyncManager`. * **Coexistence**: Firebase Sync (MotorCloud) and GitHub Sync should coexist as independent sync destinations. Riders can enable either, both, or neither. * **Deprecation Horizon**: Retain GitHub sync for forensic engineering field-testing; position Firebase/Cloud storage as the primary consumer backup and multi-device synchronization engine. --- ## 4. Phase 1 Authentication Integration Analysis Phase 1 introduced clean, decoupled identity types: * `AuthUser`: Contains `uid`, `email`, `displayName`, `photoUrl`, `isAnonymous`, `providerId`. * `AuthState`: Represents `status` (`initial`, `loading`, `authenticated`, `unauthenticated`, `anonymous`, `error`), holding `isFirebaseConfigured`. * `authNotifierProvider`: Riverpod provider exposing reactive auth updates. ### Decoupled Identity Association * When a ride is completed, the local database record remains pure (Schema 5). * When the `SyncManager` prepares an outbox payload for cloud upload, it inspects the current `AuthUser` at sync time. * If the user is authenticated (or anonymous), the cloud document metadata is populated with `userId: authUser.uid`. * If the user is unauthenticated / local-only, cloud sync remains disabled for that ride until an account is linked or manual backup is requested. --- ## 5. Local Data Ownership Model MotorSignal follows a strict **Local-First Authoritative Model**: ``` [Rider Device] Local SQLite (Drift Schema 5) <── Master Source of Truth for Raw Data │ ├── Rides (Summary & Aggregates) ├── TelemetrySamples (20 Hz Raw Sub-Second Points) ├── RideEvents (Discrete Driving Dynamic Events) ├── PerformanceRuns (0-60 / 0-100 Benchmarks) └── GarageMotorcycles (Vehicle Profiles) [Cloud Storage (Firebase / Remote)] Cloud Records <── Read-Replica, Backup, & Multi-Device Relay │ ├── Firestore: /users/{uid}/rides/{rideId} (Aggregates & Events) └── Cloud Storage: /users/{uid}/rides/{rideId}/telemetry.zip (Compressed Raw Stream) ``` ### Core Ownership Rules 1. **Local Primacy**: Deleting a remote cloud backup must never delete the local SQLite record on the device that recorded it unless the user explicitly executes a local wipe. 2. **Immutability of Historical Telemetry**: Once a ride transitions to `RideState.finished`, its raw sensor trace is cryptographically immutable. Cloud sync uploads a frozen snapshot. 3. **Multi-Device Replication**: When a second device (Phone B) logs into the same account, cloud sync pulls ride summaries and telemetry bundles into Phone B's local database, marking them as `isSynced: true`. --- ## 6. Proposed Cloud Architecture A hybrid storage model separating **structured queryable metadata** from **high-volume raw telemetry streams** is architecturally superior: ``` ┌────────────────────────────────────────────────────────────────────────┐ │ CLOUD ENGINE │ │ │ │ ┌────────────────────────────────┐ ┌───────────────────────────────┐ │ │ │ CLOUDFIRESTORE (METADATA) │ │ CLOUD STORAGE (RAW BUNDLES) │ │ │ │ │ │ │ │ │ │ • Ride Summaries & Stats │ │ • telemetry.csv.gz / .zip │ │ │ │ • Discrete Events & Alerts │ │ • forensic_session.json.gz │ │ │ │ • Performance Benchmarks │ │ • Full GPX Trace │ │ │ │ • Garage Motorcycle Profiles │ │ • High-Res IMU Stream │ │ │ │ • Sync Metadata & ETag │ │ • Future Video / Thumbnails │ │ │ └────────────────────────────────┘ └───────────────────────────────┘ │ └───────────────────────────────────▲────────────────────────────────────┘ │ Uploads via SyncProvider ┌───────────────────────────────────┴────────────────────────────────────┐ │ MOTORCLOUD SYNC MANAGER │ │ Background Isolate Pipeline: Compress 20Hz ──► Generate JSON/CSV │ │ Exponential Backoff Queue ──► Network State Gate (Wi-Fi / Cellular) │ └───────────────────────────────────▲────────────────────────────────────┘ │ Consumes Outbox Items ┌───────────────────────────────────┴────────────────────────────────────┐ │ LOCAL PERSISTENCE & TELEMETRY │ │ AppDatabase (Schema 5) ◄── Authoritative 20 Hz Clock │ └────────────────────────────────────────────────────────────────────────┘ ``` --- ## 7. Cloud Provider Comparison | Evaluation Metric | Option A: Firestore Only | Option B: Firestore + Cloud Storage (Recommended) | Option C: Custom REST Backend | Option D: Supabase (Postgres) | | :--- | :--- | :--- | :--- | :--- | | **Telemetry Volume ($18\text{k}$ samples/hr)** | ❌ Exceeds doc limit ($1\text{ MB}$); extreme write cost | ✅ Compressed bundle ($<500\text{ KB}$) in Storage; zero write cost per sample | ✅ Efficient via custom binary / gRPC endpoints | ⚠️ Large row volume in Postgres; high egress | | **Queryability (Stats, History, Filter)** | ✅ Rich querying by date, bike, speed | ✅ Rich querying for metadata in Firestore | ✅ Full SQL / Postgres queries | ✅ Direct relational queries via SQL | | **Cost Efficiency** | ❌ Highly expensive (18,000 writes/ride) | ✅ Extremely cheap (1 doc write + 1 storage upload per ride) | ⚠️ Requires dedicated VPS/Server hosting | ✅ Affordable at baseline tier | | **Offline Resilience & SDK** | ✅ Mature SDK with offline cache | ✅ Mature SDK with background resumable upload | ⚠️ Must build custom offline sync engine | ✅ Good offline client ecosystem | | **Maintenance Burden** | Low | Low (Serverless) | High (Database, Auth, API, OS maintenance) | Medium (Managed Postgres) | **Verdict**: **Option B (Firestore + Firebase Cloud Storage)** provides the optimal balance of zero infrastructure maintenance, low cloud cost, high query performance for UI history, and full storage capacity for forensic telemetry streams. --- ## 8. Recommended Provider Architecture To support future backend evolution (or multi-destination sync), cloud sync must be defined behind an abstract provider contract: ```dart abstract class SyncProvider { String get providerId; // 'firebase', 'github', 'supabase' Future isAvailable(); Future uploadRide(SyncPayload payload); Future> fetchRemoteHeaders({DateTime? since}); Future downloadRideBundle(String remoteId); Future deleteRemoteRide(String remoteId); } ``` The application interacts solely with `SyncManager`, which coordinates the local outbox, monitors network connectivity, dispatches tasks to registered `SyncProvider`s, and reports status to Riverpod. --- ## 9. Proposed Cloud Data Model ### Firestore Document Schema #### User Profile Document: `/users/{uid}` ```json { "uid": "aB3kL99zX...", "email": "rider@motorsignal.app", "displayName": "Apex Pilot", "photoUrl": "https://...", "isAnonymous": false, "createdAt": "2026-08-31T01:00:00.000Z", "lastActiveAt": "2026-08-31T12:00:00.000Z", "schemaVersion": 1, "stats": { "totalRides": 42, "totalDistanceMiles": 1284.5, "totalMovingTimeSeconds": 89400, "maxSpeedMph": 118.4, "maxLeanLeft": 46.2, "maxLeanRight": 48.7 } } ``` #### Ride Metadata Document: `/users/{uid}/rides/{remoteRideId}` ```json { "remoteRideId": "ride_20260831_080000_aB3k", "localRideId": 14, "deviceUuid": "e7b1a2c3-d4e5-4f6a-8b9c-0d1e2f3a4b5c", "startTime": "2026-08-31T08:00:00.000Z", "endTime": "2026-08-31T08:45:12.000Z", "distanceMiles": 24.8, "topSpeedMph": 74.2, "averageSpeedMph": 41.5, "maxLeanLeft": 32.1, "maxLeanRight": 35.4, "maxAccelG": 0.58, "maxBrakingG": -0.88, "movingTimeSeconds": 2160, "stoppedTimeSeconds": 552, "elevationGainMeters": 312.0, "elevationLossMeters": 298.0, "qualityScore": 98, "qualityRating": "EXCELLENT", "motorcycle": { "year": 2001, "make": "Yamaha", "model": "YZF-R6", "variant": "Factory Stock" }, "storageBundle": { "storagePath": "users/aB3kL99zX.../rides/ride_20260831_080000_aB3k/telemetry.zip", "bundleSizeCompressedBytes": 348120, "sampleCount": 27120, "contentSha256": "8f4b2c1..." }, "software": { "version": "1.0.2", "build": 102, "gitCommit": "00819ec", "telemetrySchemaVersion": 1, "exportSchemaVersion": 1 }, "syncMetadata": { "uploadedAt": "2026-08-31T08:46:00.000Z", "clientTimestamp": "2026-08-31T08:45:50.000Z", "isDeleted": false } } ``` #### Discrete Events Subcollection: `/users/{uid}/rides/{remoteRideId}/events/{eventId}` ```json { "eventId": "evt_01", "timestamp": "2026-08-31T08:14:22.050Z", "eventType": "Hard Braking", "value": -0.88, "latitude": 37.7749, "longitude": -122.4194 } ``` #### Performance Benchmarks Subcollection: `/users/{uid}/rides/{remoteRideId}/performance/{runId}` ```json { "runId": "perf_01", "type": "0-60", "timeMs": 3420.0, "topSpeedReachedMph": 60.2, "timestamp": "2026-08-31T08:20:10.000Z" } ``` --- ## 10. Local Sync Outbox Design ### Preservation of Schema 5 To preserve the frozen Drift Schema 5 database without requiring high-risk migrations, the local synchronization queue is implemented via an **Outbox File Manifest** (`sync_outbox_manifest.json`) backed by `SharedPreferences` index pointers. ### Outbox Model Structure ```dart class SyncItem { final int localRideId; final String idempotencyKey; final String providerId; final SyncStatus status; final int attemptCount; final DateTime createdAt; final DateTime? lastAttemptAt; final DateTime? nextRetryAt; final DateTime? uploadedAt; final String? remoteId; final String? lastError; final String? bundleLocalPath; const SyncItem({ required this.localRideId, required this.idempotencyKey, required this.providerId, required this.status, this.attemptCount = 0, required this.createdAt, this.lastAttemptAt, this.nextRetryAt, this.uploadedAt, this.remoteId, this.lastError, this.bundleLocalPath, }); } ``` --- ## 11. Sync State Machine ``` ┌───────────────────────┐ │ LOCAL_ONLY │ └──────────┬────────────┘ │ Ride finished / Cloud sync enabled ▼ ┌───────────────────────┐ │ QUEUED │ └──────────┬────────────┘ │ ┌────────────┴────────────┐ No Network / Metered Network Connected & Authed ▼ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ │ WAITING_FOR_NETWORK │ │ PREPARING │ (Isolate Bundle Compression) └────────────┬────────────┘ └────────────┬────────────┘ │ Network restored ▼ └────────────────────────►┌─────────────────────────┐ │ UPLOADING │ └───────────┬─────────────┘ │ ┌─────────────┴─────────────┐ Upload Success Upload Failure ▼ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ │ SYNCED │ │ RETRY_WAIT │ └─────────────────────────┘ └────────────┬────────────┘ │ Exponential backoff ▼ ┌─────────────────────────┐ │ FAILED (Max Retries) │ └─────────────────────────┘ ``` --- ## 12. Retry & Failure Strategy 1. **Exponential Backoff Formula**: $$T_{\text{delay}} = \min\left(T_{\text{max}},\; T_{\text{base}} \times 2^{\text{attempt}} + \text{jitter}\right)$$ * $T_{\text{base}} = 5\text{ seconds}$ * $T_{\text{max}} = 30\text{ minutes}$ * Maximum automated retry limit: **5 attempts**, after which status becomes `FAILED_MANUAL_REQUIRED`. 2. **Network Constraint Filtering**: * Settings configurable: `Sync over Cellular` vs `Wi-Fi Only`. * Unmetered network gate: Automatically suspends large bundle uploads if device switches to roaming or restricted metered networks. 3. **Decoupled Execution**: All retry timers and network listeners execute asynchronously in background services. The 20 Hz telemetry clock is completely isolated and unaffected by sync failures or retries. --- ## 13. Duplicate & Idempotency Strategy To prevent duplicate cloud entries when uploading across flaky connections: 1. **Deterministic Remote ID**: $$\text{remoteRideId} = \text{SHA256}(\text{deviceInstallationUuid} + \text{"\_"} + \text{startTimeIso8601})$$ 2. **Idempotent Upsert**: * Firestore writes use `set(..., SetOptions(merge: true))` targeted at `/users/{uid}/rides/{remoteRideId}`. * Storage uploads overwrite the canonical destination `users/{uid}/rides/{remoteRideId}/telemetry.zip`. 3. **Payload Verification**: * Cloud Storage upload includes `contentSha256` in custom metadata. If an upload was interrupted, the client checks remote file hash before re-uploading bytes. --- ## 14. Anonymous Account Linking ``` [Local Anonymous Rider] ──► Records Rides ──► AuthState.anonymous (Local ID: anon_123) │ │ Rider signs in with Google / Email ▼ [Firebase Auth: linkWithCredential] │ ┌─────────────────────────────┴─────────────────────────────┐ ▼ ▼ [Linking Successful] [Linking Conflict / New Account] • UID remains constant • New UID generated • Remote documents retain same path • Outbox queue updates local metadata • Zero data re-upload required • Local rides queued for backup to new UID ``` ### Safety Rules: * If account linking fails (e.g., Google account already exists on another UID), prompt the rider: *"Link this device's rides to your existing Google Account?"* * If approved, the Outbox re-tags local un-synced rides with the existing account UID and schedules background sync. * Signing out immediately clears in-memory user tokens and halts active sync streams, leaving local SQLite databases completely untouched. --- ## 15. Multi-Device Synchronization ### Ingestion Protocol for Secondary Devices (Phone B) 1. **Header Sync**: Phone B queries `/users/{uid}/rides` where `syncMetadata.uploadedAt > lastSyncTime`. 2. **Local Placeholder Insertion**: Phone B inserts summary headers into its local Drift `Rides` table with a flag indicating remote origin. 3. **Selective / On-Demand Telemetry Download**: * Summary, route map coordinates, and stats display immediately in History. * Raw 20 Hz telemetry bundles (`telemetry.zip`) are downloaded on-demand only when the rider opens Forensic Analysis, Replay, or Export for that session. 4. **Deletes & Tombstones**: * Deleting a ride on Phone A sets `syncMetadata.isDeleted = true` (soft delete) in Firestore. * Phone B syncs the tombstone and marks its local copy as deleted or removes it per user preference. --- ## 16. Security Model ### Firestore Security Rules (`firestore.rules`) ```javascript rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; match /rides/{rideId} { allow read, write: if request.auth != null && request.auth.uid == userId; match /{allSubcollections=**} { allow read, write: if request.auth != null && request.auth.uid == userId; } } } } } ``` ### Cloud Storage Security Rules (`storage.rules`) ```javascript rules_version = '2'; service firebase.storage { match /b/{bucket}/o { match /users/{userId}/rides/{rideId}/{allPaths=**} { allow read, write: if request.auth != null && request.auth.uid == userId; } } } ``` ### Security Highlights * **Zero Cross-User Data Exposure**: All user data is keyed directly under `/users/{request.auth.uid}/`. * **Zero Hardcoded Secrets**: Client configuration relies purely on `google-services.json` / `GoogleService-Info.plist` without committing API secrets or GitHub PATs to source control. --- ## 17. Privacy Model Motorcycle telemetry contains sensitive personal data (precise GPS home locations, maximum speed, dynamic acceleration). The privacy model enforces user-selectable tiers: ``` [Privacy Tier 1: Local Only (Default)] • No data leaves device; 100% offline persistence. [Privacy Tier 2: Private Cloud Backup] • Full forensic sync to user's encrypted, authenticated private storage. • Not visible to public search, community leaderboards, or third parties. [Privacy Tier 3: Community & Fleet Sharing (Future)] • Anonymized aggregate stats (road condition hazard alerts, cornering heatmaps). • Strips first/last 0.5 miles of GPS coordinates to protect origin/destination privacy. ``` --- ## 18. Camera & Media Future Compatibility When camera hardware or action cameras (GoPro / Insta360 / BLE Dashcams) are integrated in later phases: ``` Ride Session ├── Firestore: /users/{uid}/rides/{rideId} (Metadata & Video Manifest) ├── Cloud Storage: .../telemetry.zip (Raw 20 Hz Telemetry) └── Cloud Storage: .../media/ ├── video_segment_01.mp4 (Resumable chunked upload) ├── thumbnail.jpg (Instant preview) └── bookmarks.json (Dynamic telemetry trigger highlights) ``` * **Storage Isolation**: Video files ($100\text{ MB} - 4\text{ GB}$) bypass Firestore entirely and utilize Firebase Storage Resumable Upload URLs. * **Telemetry Synchronicity**: Video timestamps are indexed against the monotonic 20 Hz telemetry timestamps for frame-accurate playback. --- ## 19. GitHub Provider Compatibility The proposed `SyncManager` architecture natively incorporates GitHub sync alongside Firebase: ```dart class GitHubSyncProvider implements SyncProvider { final GitHubSyncService _service; @override String get providerId => 'github'; ... } class FirebaseSyncProvider implements SyncProvider { final FirebaseFirestore _firestore; final FirebaseStorage _storage; @override String get providerId => 'firebase'; ... } ``` * Both providers consume the same standardized `SyncPayload` generated by `FieldTestExportService`. * A rider can configure GitHub PAT for engineering test uploads and Firebase for consumer account sync simultaneously. --- ## 20. Performance & Isolate Strategy To maintain a rock-solid 20 Hz telemetry loop and a 60/120 FPS UI: 1. **Background Isolate Compression (`compute()`)**: * Reading 18,000 samples from SQLite, encoding JSON/CSV, and executing GZIP/ZIP compression must run in a dedicated background worker isolate: ```dart final zipBytes = await compute(_packageRideBundleIsolate, exportParams); ``` 2. **Streamed Storage Uploads**: * Compressed files are written to temporary application storage and streamed directly to Cloud Storage via resumable background tasks, avoiding RAM spikes. --- ## 21. Failure Scenario Matrix | # | Scenario | Failure Mode | Architectural Recovery Strategy | | :--- | :--- | :--- | :--- | | **1** | Phone has no internet | Network connection dropped | Ride saves to local Drift SQLite; marked `WAITING_FOR_NETWORK` in outbox. | | **2** | Firebase is unavailable | HTTP 503 / Backend timeout | Outbox backs off exponentially ($5\text{s} \rightarrow 30\text{m}$). Telemetry continues. | | **3** | User is logged out | Missing auth token | Sync paused with `AUTH_REQUIRED`; local ride remains available in UI. | | **4** | User is anonymous | Ephemeral local UID | Sync operates using Firebase Anonymous Auth; ready for account linking. | | **5** | Auth token expires | HTTP 401 | `AuthService` auto-refreshes token; queue retries automatically. | | **6** | Ride finishes during loss | No network on finish | `RideStateMachine` finishes locally in 0ms; queues outbox asynchronously. | | **7** | App killed during upload | Process termination | Outbox marks upload incomplete; resumes chunked upload on next app boot. | | **8** | Battery dies during upload | Sudden device shutdown | Incomplete remote files overwritten idempotently on next startup. | | **9** | Upload partially completes | Firestore OK, Storage failed | Outbox tracks sub-step completion; retries only the storage payload. | | **10** | Same ride uploaded twice | Retried network request | Deterministic `remoteRideId` ensures idempotent Firestore merge (no duplicates). | | **11** | User signs out mid-upload | Session invalidated | Active upload stream cancelled immediately; local file preserved. | | **12** | User switches accounts | New account credentials | Outbox isolates queue per `uid`; clears foreign upload cache. | | **13** | Cloud record deleted | Remote delete via console | Local database retains authoritative copy; re-syncs if requested. | | **14** | Local ride deleted | Rider deletes from History | Tombstone event sent to cloud if sync enabled; otherwise purely local wipe. | | **15** | Corrupt export bundle | CRC32 mismatch | Isolate regenerates bundle from authoritative SQLite records. | | **16** | Cloud quota exceeded | HTTP 402/429 | Outbox sets `QUOTA_EXCEEDED`; notifies user via Settings/Account badge. | | **17** | Firestore write fails | Security rule or index error | Logs error code to diagnostics; schedules retry without crashing app. | | **18** | Storage upload fails | Upload timeout / network drop | Resumable upload session recovers at exact byte offset when online. | | **19** | GitHub OK, Firebase failed | Partial provider success | `SyncManager` tracks provider statuses independently per ride. | | **20** | Firebase OK, GitHub failed | Partial provider success | Firebase marked `SYNCED`; GitHub remains queued for retry. | --- ## 22. Historical Ride Migration Strategy * **Zero Forced Migrations**: Existing local rides created prior to Phase 2 are tagged as `LOCAL_ONLY`. * **User-Initiated Cloud Backup**: * In `Settings > Account`, riders are presented with: > *"Found 12 offline rides. Back up to MotorCloud?"* * User can tap `Back Up All Historical Rides` or select individual rides from History. * Uploads are throttled in background isolates to avoid bandwidth saturation. --- ## 23. UI / UX Recommendations ``` Settings └── Account & Cloud Sync ├── Account Profile Header (Email, Display Name, UID, Provider) ├── Sync Status Badge (● Synced 2m ago | ○ 3 rides pending | ⚠ Offline) ├── [Sync Now] Manual Trigger Button ├── Network Preferences: │ ├── [Toggle] Sync over Cellular Data (Default: OFF) │ └── [Toggle] Sync on Wi-Fi Only (Default: ON) ├── Cloud Storage Management: │ ├── Usage Meter: 14.2 MB / 10 GB │ └── [Button] Back Up Historical Offline Rides └── Privacy & Data: └── [Selector] Private Backup vs Local Only ``` * **Cockpit Safety Rule**: During an active ride (`RideState.riding`), all sync notifications and prompts are completely suppressed. --- ## 24. Observability & Diagnostics The existing Diagnostics screen (`/settings/diagnostics`) will expose a new diagnostic section: ``` [CLOUD SYNCHRONIZATION DIAGNOSTICS] • Engine State: IDLE (Connected to MotorCloud) • Active Account: rider@motorsignal.app (UID: aB3kL99zX...) • Outbox Queue Length: 0 pending • Total Cloud Synced Rides: 34 • Last Successful Sync: 2026-08-31 01:25:12 • Last Sync Latency: 1.42s (Isolate: 240ms, Doc: 380ms, Storage: 800ms) • Network Policy: Wi-Fi Only (Current: Wi-Fi connected) • GitHub Sync Provider: ACTIVE (bponders12-cell/ride_logs) ``` --- ## 25. Database Schema Impact ### Drift Schema 5 Remains 100% Frozen * **No Schema Changes in Phase 2**: All outbox queues, retry timestamps, and remote document IDs are managed outside Drift tables (via `sync_outbox_manifest.json` and `SharedPreferences`). * **Future Schema 6 (Deferred)**: If future bidirectional multi-device sync warrants a dedicated relational table, it will be introduced with an explicit, fully tested Drift migration without altering raw telemetry schemas. --- ## 26. Architectural Risks & Mitigations 1. **Risk: Main Thread Isolate Lag during ZIP Serialization** * *Mitigation*: Strictly execute `FieldTestExportService` compression inside `compute()` worker isolates. 2. **Risk: High Cloud Storage Costs from Raw Telemetry** * *Mitigation*: Store raw 20 Hz points exclusively in compressed GZIP/ZIP archives in Cloud Storage; store only summary metrics and events in Firestore. 3. **Risk: Unintentional Coupling to `RideStateMachine`** * *Mitigation*: Decouple `RideStateMachine` from sync by listening to state machine events externally via Riverpod listeners rather than adding sync calls inside `finishRide()`. --- ## 27. Recommended Phase 2 Implementation Plan ``` Phase 2.1: Sync Foundation & Outbox Engine ├── Implement SyncItem outbox model & manifest persistence ├── Implement background worker isolate for ZIP packaging └── Build SyncManager with exponential backoff & network monitoring Phase 2.2: Firebase Cloud Provider Implementation ├── Implement FirebaseSyncProvider (Firestore doc + Storage bundle) ├── Configure Firestore security rules & Cloud Storage security rules └── Refactor GitHubSyncService into GitHubSyncProvider Phase 2.3: Riverpod State & UI Integration ├── Expose syncStateProvider, syncOutboxProvider, pendingRidesProvider ├── Add Sync Management card to AccountScreen and Settings └── Integrate sync diagnostics into DiagnosticsScreen Phase 2.4: Multi-Device Sync & Testing ├── Implement remote ride header fetch & on-demand bundle download ├── Write 15+ comprehensive unit & mock sync failure tests └── Verify 100% isolation of 20 Hz TelemetryService and Schema 5 ``` --- ## 28. Explicit "DO NOT MODIFY" Boundaries The following files and subsystems remain **STRICTLY FROZEN**: 1. `lib/services/telemetry_service.dart` (20 Hz acquisition cadence, 50 ms monotonic clock) 2. `lib/services/sensor_fusion_service.dart` (Complementary filtering, vehicle coordinates) 3. `lib/services/location_service.dart` (GPS filtering & accuracy gating) 4. `lib/services/compass_service.dart` (Heading acquisition) 5. `lib/services/validation/telemetry_validator.dart` (Data quality rules) 6. `lib/features/rides/ride_state_machine.dart` (3.0 MPH gate, 3s DB batching, crash recovery) 7. `lib/database/database.dart` & `tables.dart` (Drift Schema 5) 8. `lib/services/field_test/field_test_export_service.dart` (Forensic export schemas) --- ## 29. Verification Checklist - [x] All 29 discovery sections comprehensively analyzed and structured. - [x] Authoritative 20 Hz telemetry pipeline verified as 100% independent from cloud services. - [x] Drift Schema 5 confirmed intact with zero schema migrations required. - [x] Existing GitHub sync architecture analyzed and positioned for clean provider abstraction. - [x] Phase 1 authentication integration mapped cleanly without telemetry coupling. - [x] All 20 failure scenarios analyzed with concrete recovery behaviors. - [x] Repository state verified via `git status --short`, `git diff --stat`, and `git diff --name-only`. --- ## RECOMMENDED NEXT IMPLEMENTATION When Phase 2 execution begins, work should be sequenced into three strict tiers: ### MUST HAVE (Phase 2 Core) 1. **`SyncManager` & Outbox Engine**: Non-blocking outbox queue persisting sync status in `sync_outbox_manifest.json` outside Drift Schema 5. 2. **`FirebaseSyncProvider`**: Dual-destination upload sending structured summary/events to Firestore and compressed raw telemetry bundles to Cloud Storage. 3. **Background Worker Isolate**: Offloading bundle compression and JSON/CSV serialization to `compute()`. 4. **Decoupled Riverpod Observer**: Moving sync triggers out of `RideStateMachine` into an external `RideCompletionSyncObserver`. 5. **Account Screen Sync Controls**: Manual "Sync Now", Wi-Fi only toggle, and sync status indicator in `AccountScreen`. ### SHOULD HAVE (Phase 2 Refinement) 1. **`GitHubSyncProvider` Abstraction**: Unifying the existing GitHub sync behind the `SyncProvider` interface. 2. **Historical Rides Backup Action**: UI button allowing users to back up existing `LOCAL_ONLY` rides. 3. **Sync Diagnostics Panel**: Adding sync metrics to the Diagnostics screen. ### DEFER (Phase 3+) 1. **Two-Way Multi-Device Editing**: Conflict resolution for edited ride metadata across multiple active devices. 2. **Camera / Video Media Sync**: Resumable multi-gigabyte video chunk synchronization. 3. **Public Community Leaderboards & Social Feeds**: Public discovery and shared ride feeds. 4. **Drift Schema 6 Migration**: Introducing dedicated relational tables for sync outbox records.