critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

ProxyActivityRepository implements IProxyActivityRepository interface and is registered as a Riverpod Provider
insertProxyActivity(String coordinatorId, String attributedMentorId, ProxyActivityData activityData) returns Future<ProxyActivity> — the coordinator_id and attributed_mentor_id are always mapped to their correct table columns and never swapped
An assertion (assert or explicit throw) in insertProxyActivity prevents coordinatorId == attributedMentorId from being passed, surfacing this as a domain error before reaching the database
insertBulkProxyActivities(List<ProxyActivityInsert> activities) performs a single Supabase batch insert (not N individual inserts) and returns Future<List<ProxyActivity>>; partial failures roll back the entire batch via a Supabase transaction or by catching the error and returning a BulkInsertFailureException
fetchProxyActivitiesByCoordinator(String coordinatorId, {DateRange? range}) returns Future<List<ProxyActivity>> filtered by coordinator_id and optional date range
fetchProxyActivitiesByMentor(String mentorId, {DateRange? range}) returns Future<List<ProxyActivity>> filtered by attributed_mentor_id and optional date range — this is the query used for Bufdir reporting
All Supabase errors are wrapped in ProxyActivityRepositoryException subtypes; raw PostgrestException never propagates
Integration tests demonstrate single insert, bulk insert (5 records), and both fetch variants

Technical Requirements

frameworks
Flutter
Riverpod
Supabase
apis
Supabase Dart client
proxy_activities table
Supabase batch insert API
data models
ProxyActivity
ProxyActivityData
ProxyActivityInsert
proxy_activities
performance requirements
insertBulkProxyActivities must use a single batch insert call — never loop individual inserts for bulk operations
fetchProxyActivitiesByMentor must complete in under 800ms for up to 500 records (used in Bufdir report generation)
Use .select('id, coordinator_id, attributed_mentor_id, activity_type, date, duration_minutes, notes, is_recurring, template_id') — explicit column selection, no wildcards
security requirements
Never accept coordinator_id from client input alone — always derive it from the authenticated Supabase session (auth.uid()) and validate that the passed coordinatorId matches
Validate that attributedMentorId is not null, not empty, and not equal to coordinatorId before making any Supabase call
Bulk insert must validate every record in the list before submitting — reject the entire batch if any record has invalid attribution fields

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

The single most important implementation detail: insertProxyActivity must always write coordinator_id = coordinatorId and attributed_mentor_id = attributedMentorId — never the reverse. Add a method-level comment explicitly documenting this mapping. Use freezed for ProxyActivity, ProxyActivityData, and ProxyActivityInsert models. For bulk insert, Supabase Dart client supports .insert(List) — use this to submit all records in one call.

The fetchProxyActivitiesByMentor method is architecturally different from fetchProxyActivitiesByCoordinator — it queries on attributed_mentor_id (Bufdir's view of the data) rather than coordinator_id (the administrative view). Never merge these two methods or make attributed_mentor_id an optional filter on a single method, as this would risk returning wrong data for Bufdir reports. DateRange filtering should use .gte('date', range.start.toIso8601String()).lte('date', range.end.toIso8601String()).

Testing Requirements

Unit tests (flutter_test with mocktail): (1) insertProxyActivity correctly maps coordinatorId to coordinator_id column and attributedMentorId to attributed_mentor_id column — assert the Supabase client receives the correct payload, (2) insertProxyActivity throws DomainException when coordinatorId == attributedMentorId, (3) insertBulkProxyActivities calls Supabase with a list payload (not multiple individual calls), (4) fetchProxyActivitiesByMentor applies attributed_mentor_id filter (not coordinator_id), (5) Supabase errors map to correct exception types. Integration tests: run all four methods against local Supabase instance with test data. Minimum 85% line coverage.

Component
Proxy Activity Repository
data medium
Epic Risks (3)
high impact medium prob security

Supabase RLS policies for org-scoped proxy access may be difficult to express correctly, especially for coordinators with multi-chapter access. An overly permissive policy could allow cross-org proxy registrations, corrupting Bufdir reporting; an overly restrictive policy could block legitimate coordinators from registering.

Mitigation & Contingency

Mitigation: Write integration tests covering all access boundary cases (same org, cross-org, multi-chapter coordinator) before merging any RLS migration. Use parameterised RLS test helpers already established by the auth feature.

Contingency: If RLS proves insufficient, add a server-side Edge Function validation layer that re-checks org membership before persisting any proxy record, providing defence in depth.

medium impact low prob technical

Adding new tables and foreign key constraints to an existing production Supabase database risks migration failures or locking issues if the database already contains active sessions during deployment.

Mitigation & Contingency

Mitigation: Use additive-only migrations (no DROP or ALTER on existing tables). Test full migration sequence in a staging Supabase project before production deployment. Schedule during low-traffic window.

Contingency: Maintain a rollback migration script. If the migration fails, the feature remains unreachable behind a feature flag while the schema issue is resolved.

high impact medium prob security

Audit log entries must be immutable for compliance, but Supabase RLS by default allows row owners to update their own rows. If audit records are accidentally mutable, dispute resolution and accountability guarantees are invalidated.

Mitigation & Contingency

Mitigation: Configure the proxy_audit_log table with an RLS policy that allows INSERT for coordinators but denies UPDATE and DELETE for all roles including service_role, enforced at the database level.

Contingency: If RLS cannot fully prevent updates, create a database trigger that reverts any UPDATE to the audit table and logs the attempt as a security event.