critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

ProxyRegistrationService exposes a single async method: `Future<ProxyRegistrationResult> registerProxyActivity(ProxyRegistrationRequest request)`
Step 1: ActivityAttributionValidator is invoked first — if it returns a ValidationFailure, the service returns ProxyRegistrationResult.validationFailure(failure) without touching the database
Step 2: On ValidationSuccess, proxy_activity_repository.create() is called with the request data and dual attribution (coordinator_id as proxy_author, peer_mentor_id as attributed_mentor)
Step 3: On successful database write, the service emits a ProxyActivityCreated domain event via the event bus / stream
ProxyRegistrationResult is a sealed class (discriminated union) with three variants: success(Activity created), validationFailure(ValidationFailure reason), networkError(String message)
Network errors (Supabase exceptions, timeout) are caught and returned as ProxyRegistrationResult.networkError — they do not propagate as uncaught exceptions
The service is stateless — it holds no mutable state between calls
The service is injectable via Riverpod (Provider or NotifierProvider) with constructor-injected dependencies
Dual attribution write is atomic — both coordinator_id and peer_mentor_id are set in a single INSERT, not two separate operations
Service method is idempotent-safe: if called twice with the same request within the duplicate window, the second call returns validationFailure.duplicateWindow (enforced by validator)
All database operations complete within 5 seconds; timeout is enforced with a configurable deadline

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter SDK
apis
Supabase PostgreSQL — activities table (INSERT with dual attribution)
data models
activity
assignment
performance requirements
End-to-end service call (validate + write) completes within 5 seconds on 4G
Validator runs synchronously (in-memory) — adds zero network latency to the flow
security requirements
coordinator_id is sourced from the server-side JWT claims — never from the client request payload to prevent spoofing
RLS on the activities table enforces that only authenticated coordinators can INSERT with proxy attribution
Service role key is never used client-side — the Supabase anon key with RLS is sufficient for this write
Input validation (non-null, valid UUIDs, date not in distant future) applied before any database call

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define ProxyRegistrationResult as a Dart sealed class with three subclasses. Use Dart 3's pattern matching (`switch (result) { case ProxyRegistrationResult.success(:final activity) => ... }`) in the BLoC layer for exhaustive handling. The domain event stream can be implemented as a simple `StreamController.broadcast()` exposed via a Riverpod provider — this keeps the service decoupled from the BLoC.

For the database write, use: `supabase.from('activities').insert({ ...requestFields, 'coordinator_id': coordinatorId, 'peer_mentor_id': mentorId, 'is_proxy': true }).select().single()`. Wrap the entire async body in a try/catch targeting `PostgrestException` and `SocketException` separately to provide meaningful error messages. Apply `Future.timeout(const Duration(seconds: 5))` on the repository call. Keep the service class under 100 lines — if it grows, extract a `ProxyActivityMapper` for JSON mapping logic.

Testing Requirements

Unit tests in `test/unit/proxy_registration_service_test.dart` using mocktail to mock ActivityAttributionValidator and ProxyActivityRepository. Test cases: (1) validator returns ValidationFailure → service returns ProxyRegistrationResult.validationFailure, repository.create() is never called, (2) validator returns ValidationSuccess + repository succeeds → service returns ProxyRegistrationResult.success with created Activity, ProxyActivityCreated event is emitted, (3) validator returns ValidationSuccess + repository throws SupabaseException → service returns ProxyRegistrationResult.networkError, no event emitted, (4) validator returns ValidationSuccess + repository times out → service returns ProxyRegistrationResult.networkError within 5s deadline. Verify event emission by subscribing to the event stream before calling the service. All tests use fake async to control timers.

Component
Proxy Registration Service
service medium
Epic Risks (3)
medium impact medium prob technical

The 2-hour window duplicate detection logic requires querying existing proxy records with compound key matching (mentor + date + activity type within time range). If the query is too broad it produces false positives that frustrate coordinators; if too narrow it misses genuine duplicates that corrupt Bufdir data.

Mitigation & Contingency

Mitigation: Define the duplicate detection window as a configurable parameter from the start. Prototype the Supabase query with representative data covering edge cases (midnight boundaries, different activity types same day, same activity type different mentors) before finalising the implementation.

Contingency: If the detection produces excessive false positives in UAT, allow coordinators to explicitly acknowledge and bypass the duplicate warning with a reason field, preserving safety while reducing friction.

high impact medium prob scope

If the proxy registration form does not clearly distinguish between the acting coordinator and the attributed mentor, coordinators may submit records attributing activities to themselves, causing inaccurate Bufdir reporting and potential funding issues.

Mitigation & Contingency

Mitigation: Conduct UAT with at least one real coordinator via TestFlight before release. Use distinct visual treatment (different card colours, explicit 'Registering on behalf of:' label) and require the confirmation screen to show both identities prominently.

Contingency: Add a mandatory confirmation checkbox on the confirmation screen that explicitly names the attributed mentor, preventing accidental self-attribution from slipping through.

high impact medium prob security

Coordinators with multi-chapter access must select an active chapter context before the mentor list is filtered correctly. If chapter scope resolution fails or is bypassed, cross-org proxy registrations could occur, violating data isolation between chapters.

Mitigation & Contingency

Mitigation: Reuse the existing active-chapter-state and hierarchy-service components established by the org hierarchy feature. Add a guard that blocks entry to the proxy flow if no chapter context is active, prompting chapter selection first.

Contingency: If the chapter resolution service is unavailable, default to the most restrictive scope (no mentors visible) and surface a clear error message rather than showing an unfiltered mentor list.