From eded81345e787d37ece57751a20dc8c659641a78 Mon Sep 17 00:00:00 2001 From: bponders12-cell Date: Tue, 1 Sep 2026 16:58:38 -0400 Subject: [PATCH] Document current to target sync architecture gap audit --- MOTORSIGNAL_SYNC_ARCHITECTURE_GAP_AUDIT.md | 85 ++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 MOTORSIGNAL_SYNC_ARCHITECTURE_GAP_AUDIT.md diff --git a/MOTORSIGNAL_SYNC_ARCHITECTURE_GAP_AUDIT.md b/MOTORSIGNAL_SYNC_ARCHITECTURE_GAP_AUDIT.md new file mode 100644 index 0000000..456784e --- /dev/null +++ b/MOTORSIGNAL_SYNC_ARCHITECTURE_GAP_AUDIT.md @@ -0,0 +1,85 @@ +# 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.