critical priority low complexity infrastructure pending backend specialist Tier 2

Acceptance Criteria

Supabase.instance.client is initialised exactly once at app startup via SupabaseClientProvider and is accessible throughout the app without re-initialisation
A Riverpod Provider (supabaseClientProvider) exposes the SupabaseClient instance and is used consistently — no direct Supabase.instance.client references in feature code
The provider correctly references the existing 074-supabase-client infrastructure without duplication or parallel singletons
A typed extension or accessor method exposes the activity_types table as a SupabaseQueryBuilder, e.g. client.activityTypes returning client.from('activity_types')
Hot restart and app resume do not cause re-initialisation or duplicate client errors
If Supabase credentials are missing or malformed at startup, the app throws a descriptive initialisation error rather than a null-pointer crash
Unit tests confirm the provider returns the same instance across multiple reads (singleton contract)
No hard-coded Supabase URL or anon key appears in source files — all secrets come from environment configuration established in task-001/002

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter SDK
apis
Supabase REST API (activity_types table)
Supabase Realtime (if live updates required)
data models
ActivityType
performance requirements
Client initialisation must complete within 500ms on cold start
Provider reads must be synchronous after initialisation (no async gap in hot path)
security requirements
Supabase URL and anon key loaded from environment/secure storage, never hard-coded
Row Level Security (RLS) enforced on activity_types table — client must not bypass RLS
Service-role key must never be used in the Flutter client

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Locate the existing 074-supabase-client file and read it fully before writing any code. The goal is extension, not replacement. If a supabaseClientProvider already exists, add the activityTypes typed accessor as an extension method on SupabaseClient rather than creating a second provider. Use Provider (not FutureProvider) so consumers receive a synchronous value after the async initialisation in main().

Pattern: initialise Supabase in main() before runApp(), then expose the already-initialised client via a simple Provider.value. Avoid StateNotifierProvider or AsyncNotifierProvider for a pure singleton — that adds unnecessary rebuild surface. The typed accessor (client.activityTypes) should be defined as an extension on SupabaseClient in a separate file (supabase_client_extensions.dart) to keep the provider file clean.

Testing Requirements

Unit tests using flutter_test and Riverpod's ProviderContainer: (1) verify supabaseClientProvider returns a non-null SupabaseClient; (2) verify multiple reads return the identical instance (singleton); (3) verify the activityTypes accessor returns a SupabaseQueryBuilder targeting 'activity_types'. Mock the Supabase SDK using a MockSupabaseClient to avoid real network calls. Integration smoke test: confirm the provider resolves successfully in the full app container during widget tests. No e2e calls to Supabase in CI — use a mock or local Supabase instance.

Component
Supabase Client
infrastructure low
Epic Risks (3)
high impact medium prob technical

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.

high impact low prob security

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.

medium impact medium prob integration

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.