# 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 ```text ┌─────────────────────────────┐ │ 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.