Local SQLite cache schema for offline summary access
epic-periodic-summaries-foundation-task-007 — Design and create the local SQLite table (via drift or sqflite) that mirrors the structure of periodic_summaries for offline read access. Include columns for last_synced_at, is_stale, and organisation_id. Write migration scripts that handle schema upgrades without data loss. This schema is the foundation for the SummaryCacheRepository.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Use drift if the rest of the project already depends on it — its type-safe query builder and migration API (`MigrationStrategy.onUpgrade`) are far safer than raw sqflite SQL strings. Define the table as a drift `Table` class so the schema is a single source of truth. Store dates as Unix milliseconds (INTEGER) rather than ISO strings to avoid locale-dependent parsing bugs. The `summary_data` column should store the full serialised `SummaryPeriodModel` JSON so the cache layer never needs to re-parse Supabase responses.
Mark `is_stale` as an integer (0/1) rather than a Dart bool because SQLite has no native boolean type — drift's `BoolColumn` handles this transparently. When writing migration scripts, always add new columns with DEFAULT values so existing rows remain valid without a full table rebuild.
Testing Requirements
Write flutter_test unit tests (in-memory database) covering: (1) schema creation succeeds and all columns exist with correct types; (2) INSERT of a valid row succeeds and SELECT returns the same values; (3) INSERT with null organisation_id throws a database exception; (4) is_stale CHECK constraint rejects values other than 0 and 1; (5) migration from v1 to v2 preserves existing rows. Use drift's in-memory database or sqflite's in-memory factory so tests run without a device. Target 100% statement coverage of the migration logic.
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.