86 lines
7.5 KiB
Markdown
86 lines
7.5 KiB
Markdown
# 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.
|