Build Riverpod ActivityTypeCacheProvider
epic-activity-type-configuration-foundation-task-009 — Implement the in-memory Riverpod provider (073-activity-type-cache-provider) that holds the full list of active activity types for the current org session. The provider should fetch from the ActivityTypeRepository on first access and serve subsequent reads from memory, avoiding redundant network calls during a single user session.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 4 - 323 tasks
Can start after Tier 3 completes
Implementation Notes
Use @riverpod annotation with keepAlive: true on the notifier to prevent auto-disposal during navigation. The orgId should be read from an authSessionProvider (already established in the project) rather than passed as a parameter, to avoid provider family complexity. Implement the single-flight guard by checking if state is already AsyncData before calling the repository — Riverpod's AsyncNotifier handles this naturally when build() is only called once. Keep the provider thin: no business logic, no filtering beyond is_active — all transformation belongs in the metadata resolver (task-011).
Annotate with @riverpod and run build_runner to generate the .g.dart file; never hand-write the generated code.
Testing Requirements
Unit tests using flutter_test and mocktail. Test cases: (1) first access triggers exactly one repository.fetchActiveByOrganisation call; (2) second access returns cached result with zero additional repository calls; (3) AsyncValue is AsyncLoading on construction before future resolves; (4) AsyncValue becomes AsyncData> on success; (5) AsyncValue becomes AsyncError when repository throws ActivityTypeNotFoundException; (6) only is_active==true types appear in the returned list; (7) provider state is empty list (not error) when org has no active types. Use ProviderContainer for isolated provider testing without Flutter widget tree.
The JSONB metadata column has no enforced schema at the database level. If the Dart model and the stored JSON diverge (e.g., a field is renamed or a new required flag is added without a migration), the metadata resolver will silently return null or throw at parse time, breaking conditional wizard logic for all organisations.
Mitigation & Contingency
Mitigation: Define a versioned Dart Freezed model for ActivityTypeMetadata and add a Supabase check constraint or trigger that validates the JSONB structure on write. Document the canonical metadata schema in a shared constants file and require schema review for any metadata field additions.
Contingency: Implement a lenient parse path in ActivityTypeMetadataResolver that returns safe defaults for missing fields and logs a structured warning to Supabase edge logs, allowing the app to degrade gracefully rather than crash.
If RLS policies on the activity_types table are misconfigured, a coordinator from one organisation could read or mutate activity types belonging to another organisation, violating data isolation guarantees required by all three client organisations.
Mitigation & Contingency
Mitigation: Write integration tests against the Supabase local emulator that explicitly assert cross-org isolation: a token scoped to org A must receive zero rows when querying org B activity types, and upsert attempts must return permission-denied errors.
Contingency: Apply an emergency RLS policy patch via Supabase dashboard without a code deploy. Audit all activity_type rows for cross-org contamination and restore from backup if any data leakage is confirmed.
If the cache invalidation call in ActivityTypeService is not reliably triggered after an admin creates, edits, or archives an activity type, peer mentors on the same device will see stale data in the registration wizard until the next app restart, leading to confusion and potential misregistrations.
Mitigation & Contingency
Mitigation: Enforce a strict pattern: ActivityTypeService always calls cacheProvider.invalidate() inside the same try block as the successful Supabase mutation, before returning to the caller. Write a widget test that verifies the cache notifier emits an updated list after a service mutation.
Contingency: Add a background Supabase Realtime subscription on the activity_types table that triggers cache invalidation automatically, providing an independent safety net independent of the service call path.