MotorSignal-App/MOTORSIGNAL_PHASE_2D_R2_MIG...

6.7 KiB

MotorSignal — Phase 2D: Firebase Storage → Cloudflare R2 Migration & Architecture

Document Version: 1.0.0
Phase: Phase 2D — Cloud Storage Migration & Cloud Sync Hardening
Status: ARCHITECTURAL SPECIFICATION & MIGRATION GUIDE


1. Executive Summary & Authoritative Principles

MotorSignal has migrated its large-object binary storage backend from Firebase Cloud Storage to Cloudflare R2, while preserving all existing offline-first guarantees, authentication models, and database contracts.

  • Drift SQLite Schema 5 remains authoritative for all raw telemetry and local ride recordings.
  • Firebase Authentication remains authoritative for rider identity (uid).
  • Cloud Firestore remains the structured cloud database for queryable metadata, summaries, and performance runs.
  • Cloudflare R2 is the high-capacity, low-cost object/blob storage layer for compressed forensic bundles (telemetry.zip).
  • Cloud synchronization is completely downstream of local persistence.

2. Previous vs. New Architecture

PREVIOUS ARCHITECTURE (Phase 2B/2C):
Local SQLite (Schema 5) ──► SyncManager ──► FirebaseSyncProvider
                                                ├── Cloud Firestore (Metadata)
                                                └── Firebase Cloud Storage (telemetry.zip)

NEW ARCHITECTURE (Phase 2D):
Local SQLite (Schema 5) ──► SyncManager ──► FirebaseSyncProvider
                                                ├── Cloud Firestore (Metadata & R2 Object Key)
                                                └── Cloudflare Worker Gateway
                                                        └── Cloudflare R2 (telemetry.zip)

3. Cloudflare Worker Gateway Architecture

Project Location

cloudflare/motorsignal-r2-worker/
├── src/
│   └── index.ts        # TypeScript Worker logic with Firebase JWT verification
├── wrangler.toml       # R2 bucket bindings & environment configuration
├── package.json        # Dependencies & scripts
└── README.md           # Deployment and testing instructions

Server-Side Path Derivation & Zero-Trust Boundary

The Flutter client NEVER communicates with Cloudflare R2 using permanent access keys or secrets (R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY).

Instead, the client sends its Firebase ID token to the Cloudflare Worker. The Worker:

  1. Validates the Firebase JWT cryptographic signature, expiration (exp), and project audience (aud).
  2. Extracts the verified sub claim (authenticatedUid).
  3. Validates the X-Remote-Ride-Id header against a strict regex (/^[a-zA-Z0-9_-]{3,100}$/).
  4. Derives the canonical R2 object key server-side:
    users/{authenticatedUid}/rides/{validatedRemoteRideId}/telemetry.zip
    
  5. Streams the binary payload directly into the Cloudflare R2Bucket binding.

4. Endpoints & API Contract

Binary Upload Endpoint

  • Method: PUT
  • Path: /r2/upload
  • Headers:
    • Authorization: Bearer <FirebaseIdToken>
    • Content-Type: application/zip
    • X-Remote-Ride-Id: ride_<localRideId>_<timestamp>
    • X-Bundle-Sha256: <sha256-hash>
    • X-App-Version: <semantic-version>
  • Body: Raw binary bytes of telemetry.zip.
  • Response:
    {
      "success": true,
      "objectKey": "users/<uid>/rides/<remoteRideId>/telemetry.zip",
      "size": 348120,
      "etag": "\"8f4b2c1...\"",
      "uploadedAt": "2026-08-31T12:00:00.000Z"
    }
    

Telemetry Download Endpoint

  • Method: GET
  • Path: /r2/download?rideId=<remoteRideId>
  • Headers:
    • Authorization: Bearer <FirebaseIdToken>
  • Response: Streamed binary telemetry.zip attachment.

5. Firestore Document Model

Firestore documents continue to reside at /users/{uid}/rides/{remoteRideId} with queryable metadata, referencing Cloudflare R2:

{
  "localRideId": 14,
  "remoteRideId": "ride_14_20260831_080000",
  "uid": "aB3kL99zX...",
  "startTime": "2026-08-31T08:00:00.000Z",
  "endTime": "2026-08-31T08:45:12.000Z",
  "distanceMiles": 24.8,
  "topSpeedMph": 74.2,
  "maxLeanLeft": 32.1,
  "maxLeanRight": 35.4,
  "maxAccelG": 0.58,
  "maxBrakingG": -0.88,
  "averageSpeed": 41.5,
  "movingTime": 2160,
  "stoppedTime": 552,
  "qualityScore": 98,
  "qualityRating": "EXCELLENT",
  "telemetryBundle": {
    "provider": "cloudflare-r2",
    "objectKey": "users/aB3kL99zX.../rides/ride_14_20260831_080000/telemetry.zip",
    "status": "uploaded",
    "bundleSizeBytes": 348120,
    "sha256": "8f4b2c1..."
  },
  "telemetryBundlePath": "users/aB3kL99zX.../rides/ride_14_20260831_080000/telemetry.zip",
  "bundleSizeBytes": 348120,
  "syncVersion": 2,
  "uploadedAt": "2026-08-31T08:46:00.000Z"
}

6. Security Model & Secrets Management

  1. Flutter App: Contains zero Cloudflare credentials, API keys, or R2 tokens.
  2. Cloudflare Worker: Managed via Cloudflare dashboard or Wrangler secrets (wrangler secret put FIREBASE_PROJECT_ID).
  3. Firestore Security Rules: Unchanged (users/{userId}/rides/{rideId} allows read/write only if request.auth.uid == userId).
  4. Git Safety: Secrets, .env, and private JSON files remain strictly excluded via .gitignore.

7. Migration & Backward Compatibility

  • Existing Local Rides: All local SQLite rides remain authoritative and fully accessible.
  • Existing Firestore Records: Legacy documents containing Firebase Storage references remain readable. New uploads write syncVersion: 2 and provider: "cloudflare-r2".
  • Outbox Continuity: The persistent local SyncOutbox continues tracking items by ${localRideId}_${providerId}.

8. Failure & Retry Behavior

Failure Mode Worker Status Client Sync State Recovery Strategy
Worker 5xx / Timeout 500 / 503 RETRY_WAIT Exponential backoff (5\text{s} \rightarrow 30\text{m})
Invalid / Expired Token 401 / 403 AUTH_REQUIRED Awaits token refresh / user login
Malformed Ride ID 400 FAILED Terminal validation error
Network Unavailable Client error WAITING_FOR_NETWORK Resumes automatically when online
R2 Success, Firestore Drop 200 OK RETRY_WAIT Re-attempts Firestore commit; R2 upload is idempotent

9. Future Camera & Video Compatibility

When video/camera features are introduced in Phase 3:

  • Video segments (100\text{ MB} - 4\text{ GB}) will be streamed to R2 under users/{uid}/rides/{remoteRideId}/video/chunk_01.mp4.
  • The Worker gateway can issue multipart chunk upload authorization URLs directly to Cloudflare R2 without routing massive video payloads through Firestore or mobile memory buffers.