critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

A ProxyActivityRecord Dart class exists, distinct from ActivityRecord, with required fields: id, peerMentorId, recordedByUserId, activityType, date, durationMinutes, and all optional fields present on ActivityRecord
ProxyActivityRecord.recordedByUserId is typed as String (non-nullable) — the compiler rejects any attempt to insert a proxy record without a coordinator ID
A ProxyActivityRepository class exists, implementing or extending ActivityRepository, that accepts a SupabaseClient dependency via constructor injection
ProxyActivityRepository.insert(ProxyActivityRecord) serializes and sends recordedByUserId in the Supabase insert payload and returns the created record with its server-assigned ID
ProxyActivityRepository.insertBatch(List<ProxyActivityRecord>) inserts multiple records in a single Supabase call, carrying recordedByUserId for each row
ProxyActivityRepository.queryByCoordinator(String coordinatorId) returns only activities where recorded_by_user_id = coordinatorId, ordered by date descending
ProxyActivityRepository.queryByMentor(String mentorId) returns activities for a specific peer mentor regardless of who recorded them (coordinator or self)
All existing ActivityRepository unit tests pass without modification after this change
ProxyActivityRepository has its own unit tests covering: successful insert, batch insert, query by coordinator, query by mentor, and error propagation for Supabase exceptions
No raw SQL strings appear in Dart code — all Supabase queries use the type-safe Flutter client builder API (.from().select().eq().insert() etc.)

Technical Requirements

frameworks
Flutter
Supabase
apis
Supabase Database API (supabase_flutter client)
data models
ProxyActivityRecord
ActivityRecord
ActivityType
performance requirements
insertBatch must use a single Supabase insert call with a list payload, not a loop of individual inserts, to minimize round trips
queryByCoordinator must add .limit(100) by default with optional pagination parameters to prevent over-fetching
security requirements
recordedByUserId must be sourced from the authenticated session (Supabase.instance.client.auth.currentUser!.id) inside the repository — never accepted as a raw caller-supplied string without validation
The repository must not expose a method that allows inserting a recorded_by_user_id that differs from the current authenticated user — enforce this at the Dart layer as a secondary check after the database RLS guard
All Supabase calls must be made with the user-scoped client (not service role) so RLS policies are enforced server-side

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use composition over inheritance if ActivityRepository has private members that cannot be cleanly overridden: class ProxyActivityRepository { final ActivityRepository _base; final SupabaseClient _client; ... }. Define ProxyActivityRecord as a standalone class (not extending ActivityRecord) to avoid Liskov substitution issues — both classes share a common interface or use a mixin for shared fields. In insertBatch, build the payload as List> and call _client.from('activities').insert(payloadList) once.

Supabase Flutter client handles list payloads natively. Enforce coordinator identity by reading the current user ID from _client.auth.currentUser?.id inside the repository method and throwing an AuthenticationException if null — this makes the contract explicit and fails fast if called in an unauthenticated state. For fromJson/toJson serialization, use explicit field name constants (e.g., static const String kRecordedByUserId = 'recorded_by_user_id') to avoid typos that would silently produce null values in the database. Document the dual-identity semantics with a Dart doc comment on the class explaining peerMentorId vs recordedByUserId.

Testing Requirements

Unit tests using flutter_test with a MockSupabaseClient (mockito-generated or manual fake): (1) insert() — assert the Supabase mock receives a payload containing both peer_mentor_id and recorded_by_user_id with correct values; assert returned ProxyActivityRecord matches server response. (2) insertBatch() — assert a single Supabase insert call is made with a list of N records; assert all N records in the payload carry recorded_by_user_id. (3) queryByCoordinator() — assert .eq('recorded_by_user_id', coordinatorId) is called; assert returned list maps correctly to List. (4) queryByMentor() — assert .eq('peer_mentor_id', mentorId) is called without a recorded_by_user_id filter.

(5) Error propagation — mock a PostgrestException from Supabase insert, assert ProxyActivityRepository wraps and rethrows as a domain-level RepositoryException. (6) Regression: run existing ActivityRepository test file and assert all tests pass. Aim for 90%+ coverage on ProxyActivityRepository methods.

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

The Proxy Registration Service must verify that the coordinator has a legitimate assignment relationship with the target peer mentor before creating a record. If this check is implemented only in application code and not enforced at the DB/RLS level, a compromised or buggy client could bypass it by calling the Supabase endpoint directly, creating fraudulent proxy records for arbitrary peer mentors.

Mitigation & Contingency

Mitigation: Implement permission validation at two levels: (1) application-layer check in Proxy Registration Service that queries the assignments table before constructing the payload, and (2) RLS policy on the activities table that restricts INSERT to rows where recorded_by_user_id matches the authenticated user AND peer_mentor_id is in the set of peer mentors assigned to that coordinator. The RLS policy is the authoritative guard; the service-layer check provides early user-facing feedback.

Contingency: If RLS policy implementation is blocked by Supabase plan constraints, implement a Supabase Edge Function as a proxy endpoint that enforces the permission check server-side before forwarding to the DB. Disable direct client inserts entirely for proxy activities.

medium impact medium prob technical

For a bulk session with 30 selected peer mentors, the Proxy Duplicate Detector must query existing activities for each mentor. If implemented as 30 sequential Supabase queries, round-trip latency could make the bulk confirmation screen feel slow (>3s), degrading coordinator experience and potentially causing timeouts.

Mitigation & Contingency

Mitigation: Implement the duplicate check as a single Supabase query using an IN clause on peer_mentor_id combined with the activity_type and date filters, returning all potential duplicates for the entire batch in one network round-trip. Group results client-side by mentor ID to produce the per-mentor warning structure.

Contingency: If the single-query approach returns too much data for very large chapters, add a database index on (peer_mentor_id, activity_type, date) and profile query time. If still insufficient, accept a short loading state on the confirmation screen with a progress indicator rather than pre-loading duplicates before navigation.