high priority medium complexity backend pending backend specialist Tier 6

Acceptance Criteria

`syncNow(orgId)` fetches all summaries for the given org from `SummaryPeriodRepository`, writes them to `SummaryCacheRepository`, and sets `is_stale = false` for all updated rows
`syncNow` returns a `SyncResult` value object containing: `synced_count`, `failed_count`, and `synced_at` timestamp
`ensureFresh(orgId, periodType, maxAgeMinutes)` calls `syncNow` only if `isCacheStale(orgId, maxAgeMinutes)` returns true; otherwise returns the cached value immediately without a network call
When `syncNow` is called and the network is unavailable, it catches the network exception, marks affected cache entries as stale, and returns a `SyncResult` with `failed_count > 0` — it does not rethrow
On app foreground event (`AppLifecycleState.resumed`), `syncNow` is automatically triggered for the active organisation via the Riverpod provider's lifecycle
After a new aggregation write event (emitted by the aggregation repository), `invalidateCache(orgId)` is called for the affected org before `syncNow` is triggered
The `SummarySyncService` is registered as a Riverpod `AsyncNotifierProvider` so callers can `await ref.read(summarySyncProvider.future)` to get the latest sync state
`syncNow` is idempotent — calling it twice in succession produces the same final cache state as calling it once
Unit tests cover all branches: fresh cache (no sync), stale cache (sync triggered), network failure (stale marked, no throw), and foreground trigger

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
SummaryPeriodRepository (remote Supabase)
SummaryCacheRepository (local SQLite)
Flutter AppLifecycleState (WidgetsBindingObserver)
data models
SummaryPeriodModel
SyncResult
periodic_summaries_cache
performance requirements
ensureFresh must return in under 5 ms when cache is fresh (no network call made)
syncNow must not block the UI thread — all I/O must be in async/await chains, never synchronous
Foreground sync must complete within 3 seconds on a normal mobile network connection
security requirements
orgId must be validated via OrgDataIsolationGuard before any sync operation proceeds
syncNow must never mix data from different organisations — each sync operation is scoped to a single orgId
Network errors must not expose internal Supabase query details in user-facing error messages

Execution Context

Execution Tier
Tier 6

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.

Component
Summary Cache Repository
data low
Epic Risks (3)
high impact medium prob security

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.

medium impact medium prob technical

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.

low impact low prob integration

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.