critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

BatchInsertRpcAdapter class exists with a single public method: Future<List<String>> insertBatch(List<ActivityRecord> records)
Method serializes each ActivityRecord to the JSON payload shape expected by insert_bulk_activities RPC
Method calls supabase.rpc('insert_bulk_activities', params: {'activities': payload}) and deserializes the returned list of UUID strings
On Supabase RPC error (PostgrestException), the adapter throws a typed BatchInsertException with a human-readable message and the original error code
On network error, the adapter throws a typed NetworkException (or equivalent app-level exception)
Empty list input returns an empty list immediately without making an RPC call
List exceeding 200 records throws a BatchSizeExceededException before making any network call
Adapter is registered as a Riverpod Provider<BatchInsertRpcAdapter> (or equivalent provider type) accessible via ref.read(batchInsertRpcAdapterProvider)
All public API is documented with Dart doc comments
No direct Supabase client references exist outside this adapter class (dependency inversion)

Technical Requirements

frameworks
Dart
Riverpod
Supabase Dart client (supabase_flutter)
apis
Supabase RPC insert_bulk_activities
data models
ActivityRecord (Dart model)
activity_records (DB table)
performance requirements
Serialization of 200 ActivityRecord objects must complete in < 10 ms on a mid-range device
No unnecessary object copies during serialization — use List.map with direct toJson()
security requirements
Never log full activity payloads at INFO level — only log record count and batch ID
Supabase client token is injected via Riverpod, not hardcoded or read from globals
recorded_by_user_id is only populated when explicitly provided — never default to auth.uid() inside the adapter

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Place the adapter in lib/infrastructure/supabase/ following the project layer conventions. Define ActivityRecord.toRpcPayload() as a separate method from the existing toJson() to avoid coupling the DB wire format to other serialization uses. Use final class modifier to prevent accidental subclassing. For the Riverpod registration, use Provider rather than StateProvider since the adapter is stateless.

Consider adding a debug-mode request/response logger behind a kDebugMode guard for easier local development. The typed exception hierarchy (BatchInsertException, BatchSizeExceededException) should be defined in a shared exceptions file, not inside the adapter file, for reuse by other RPC adapters.

Testing Requirements

Write flutter_test unit tests using a mock Supabase client (mockito or manual stub): (1) happy path returns list of ID strings matching mock response, (2) PostgrestException is rethrown as BatchInsertException with correct message, (3) empty list returns empty list without calling RPC, (4) list of 201 records throws BatchSizeExceededException before RPC call, (5) network timeout throws NetworkException. Write one integration test against a local Supabase instance that performs a real RPC call with 3 records and verifies returned IDs. Achieve > 90% line coverage on the adapter class.

Component
Batch Insert RPC Adapter
infrastructure medium
Epic Risks (2)
high impact medium prob security

Adding recorded_by_user_id to the activities table and writing correct RLS policies is error-prone: overly permissive policies would allow coordinators to record activities under arbitrary user IDs they do not manage, while overly restrictive policies would silently block valid proxy inserts. A policy defect here would either create a security vulnerability or break the entire proxy feature at runtime.

Mitigation & Contingency

Mitigation: Write RLS policies in a local Supabase emulator first. Include policy unit tests using pg_tap or supabase test helpers. Have a second reviewer check the migration SQL before merging. Explicitly test the three cases: coordinator inserting for their own mentors (should succeed), coordinator inserting for another chapter's mentors (should fail), peer mentor inserting for themselves (should succeed as before).

Contingency: If a policy defect is discovered in staging, roll back the migration with a down-migration script. Delay feature release until the policy is corrected and re-verified. Apply a feature flag to keep the proxy entry point hidden from coordinators until the fix is confirmed.

high impact low prob technical

The insert_bulk_activities RPC must behave atomically — a failure on row 7 of 12 must roll back rows 1–6. If Supabase's RPC transaction handling is misconfigured or if network interruptions cause partial acknowledgements, some peer mentors could receive duplicate or missing activity records, directly corrupting Bufdir statistics for the coordinator's chapter.

Mitigation & Contingency

Mitigation: Implement the RPC as a PostgreSQL function with explicit BEGIN/EXCEPTION/END block to guarantee atomicity. Add an integration test that inserts a batch where one row violates a unique constraint and asserts zero rows are committed. Document the transaction semantics in code comments.

Contingency: If atomicity cannot be guaranteed via RPC (e.g., due to Supabase plan limitations), fall back to a sequential insert loop with a compensating DELETE in case of partial failure, and surface a clear error to the coordinator listing which mentors failed and which succeeded.