Cache sync strategy and staleness invalidation logic
epic-periodic-summaries-foundation-task-012 — Implement the cache synchronisation logic that coordinates between SummaryPeriodRepository (remote) and SummaryCacheRepository (local). On app foreground, fetch fresh summaries for the active organisation and update the local cache. On write (new aggregation), invalidate stale cache entries for the affected org and period. Expose a SummarySyncService with syncNow(orgId) and ensureFresh(orgId, periodType, maxAgeMinutes) methods, integrated as a Riverpod provider that dependent digest card components can await.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 6 - 158 tasks
Can start after Tier 5 completes
Implementation Notes
Implement `SummarySyncService` as an `AsyncNotifier` in Riverpod so its state (last sync result) is observable. Register a `WidgetsBindingObserver` inside the notifier's `build()` method and remove it in the `dispose()` hook — this handles the foreground trigger without leaking. To react to write events, expose a stream or `StateNotifier` from the aggregation repository and `listen()` to it inside the sync service — use `ref.listen` in Riverpod to keep this reactive. The `ensureFresh` method should be a read-through cache: check staleness → if fresh return local data → if stale fetch remote → update cache → return updated data.
Use a `Mutex` or `Completer`-based lock to prevent concurrent `syncNow` calls from racing (two rapid foreground events should not issue two simultaneous Supabase queries). Keep `SyncResult` as an immutable value object (use `freezed` if already a project dependency).
Testing Requirements
Write flutter_test unit tests with mocked `SummaryPeriodRepository` and `SummaryCacheRepository` (using Mockito or manual fakes): (1) ensureFresh returns cached data without calling remote when cache is fresh; (2) ensureFresh calls syncNow when isCacheStale returns true; (3) syncNow calls cacheSummaries with the fetched models; (4) syncNow catches SocketException, marks cache stale, returns SyncResult with failed_count > 0; (5) foreground lifecycle event triggers syncNow; (6) post-write invalidation then sync sequence produces correct final state; (7) syncNow called twice produces idempotent result. Also write one integration test (using the local Supabase emulator from task-011) that verifies the full sync cycle end-to-end.
Supabase RLS policies for aggregation views are more complex than single-table policies. A misconfigured policy could silently allow a coordinator in one organisation to see data from another, causing a data breach and breaking trust with participating organisations.
Mitigation & Contingency
Mitigation: Write automated RLS integration tests that create two separate organisations with distinct data, then assert that queries authenticated as org-A users return only org-A rows. Run these tests in CI on every PR touching the database layer.
Contingency: If an RLS bypass is discovered post-deployment, immediately disable the periodic summaries feature flag, revoke affected sessions, audit access logs, notify affected organisations, and patch the policy before re-enabling.
Activity records may span multiple sessions types, proxy registrations, and coordinator bulk entries. Incorrect JOIN logic or missing filters in the aggregation query could double-count sessions or omit activity types, producing inaccurate summaries that erode user trust.
Mitigation & Contingency
Mitigation: Build a fixture dataset covering all activity registration paths (direct, proxy, bulk) and assert expected aggregated counts in integration tests before any UI consumes the repository.
Contingency: If inaccurate counts are reported post-launch, mark affected summaries as invalidated in the database and re-trigger generation once the query is corrected. Communicate transparently to affected users via an in-app banner.
The local cache must be invalidated when a new summary arrives via push notification. If the push token is stale or the FCM/APNs delivery is delayed, the device may show an outdated summary for an extended period, confusing users who see different numbers online versus offline.
Mitigation & Contingency
Mitigation: Implement a TTL on cached summaries (max 48 hours) so stale data is auto-cleared even without a push notification. Also trigger cache refresh on app foreground if the current period's summary is older than 24 hours.
Contingency: Provide a manual pull-to-refresh on the summary card that bypasses the cache and fetches directly from Supabase when a network connection is available.