critical priority low complexity infrastructure pending backend specialist Tier 4

Acceptance Criteria

`wrapCall<T>(orgId, fn)` executes `fn()` and returns its result when `orgId` matches the authenticated user's active `organisation_id`
`wrapCall` throws `OrganisationMismatchException` with a descriptive message when `orgId` is null, empty, or does not match the active organisation
`OrganisationMismatchException` is a custom Dart exception class with `orgIdProvided` and `orgIdExpected` fields for diagnostics
The guard reads the active organisation from the Riverpod auth/session provider — it does not accept the expected orgId as a parameter (it resolves it internally)
The class is registered as a Riverpod singleton (`Provider<OrgDataIsolationGuard>`) so all repositories receive the same instance
If the user's session is unauthenticated (no active org), `wrapCall` throws `OrganisationMismatchException` regardless of the provided orgId
Unit tests confirm: correct orgId proceeds, wrong orgId throws, null orgId throws, unauthenticated session throws
No repository in the periodic summaries feature calls the database without first going through `wrapCall` — enforced by code review checklist item

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
Supabase Auth (to read active session/org)
data models
UserSession
OrganisationMembership
performance requirements
wrapCall overhead must be under 1 ms (pure Dart validation, no I/O)
Guard must not make network calls — it reads from an in-memory Riverpod state provider only
security requirements
The guard must be the single enforcement point for org isolation — no repository may bypass it
OrganisationMismatchException must never expose raw session tokens or auth credentials in its message
The guard must treat an expired or null session identically to a mismatched org — both are isolation violations

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Keep this class purely synchronous in its validation path — read the active org from a synchronous Riverpod `StateProvider` or `StateNotifierProvider` that is kept up-to-date by the auth layer. The generic `wrapCall` signature enables type-safe wrapping without casting: `Future wrapCall(String? orgId, Future Function() fn)`. Throw before awaiting `fn()` to ensure no database call is ever initiated on an isolation violation.

Register the guard as a `Provider` (not `FutureProvider`) since it is synchronously available after auth. Consider adding a static `assertIsolation(String? orgId, String activeOrgId)` helper that other tests can call directly to verify guard logic without a full Riverpod container. Document the contract in a Dart `///` comment on the class: callers must always pass the orgId they believe they own; the guard cross-checks against the session truth.

Testing Requirements

Write flutter_test unit tests with a mocked Riverpod container: (1) wrapCall executes fn and returns result when orgId matches active org; (2) wrapCall throws OrganisationMismatchException with correct fields when orgId mismatches; (3) wrapCall throws when orgId is null; (4) wrapCall throws when orgId is empty string; (5) wrapCall throws when session is unauthenticated (null org in provider); (6) OrganisationMismatchException carries the correct orgIdProvided and orgIdExpected values. Use `ProviderContainer` with overrides to inject mock session states. Target 100% branch coverage.

Component
Organisation Data Isolation Guard
infrastructure 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.