high priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

Validator accepts a list of ProxyActivityRecord objects and returns a List<AttributionValidationResult> with one result per input record
Each result contains: mentorId, isValid (bool), and a list of ValidationError objects with error code and human-readable message
Validation rules enforced: mentor status is 'active' (not paused, suspended, or removed), mentor belongs to the coordinator's chapter_id, activity date is not in the future, activity date is not more than 90 days in the past
A paused mentor produces a specific MENTOR_PAUSED error code distinct from MENTOR_INACTIVE
A mentor from a different chapter produces MENTOR_CHAPTER_MISMATCH error code
Duplicate activity detection: if a record with the same mentor_id + activity_date already exists, produces DUPLICATE_ACTIVITY error code
Validator performs batch lookups (single Supabase query per validation rule across all mentors) rather than one query per mentor
Service is pure (no side effects) — does not modify any database state
All validation error messages are in English and suitable for display in the UI

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase REST API (read peer_mentors, proxy_activities tables)
data models
ProxyActivityRecord
AttributionValidationResult
ValidationError
MentorProfile
performance requirements
All validation for a batch of up to 50 records must complete in under 3 seconds
Use batched Supabase queries with `.in_()` filter — never N+1 per mentor
security requirements
Chapter membership check must be enforced via server-side RLS, not only client-side logic
Coordinator's chapter_id must be sourced from the authenticated session, not from a client-supplied parameter
Validation service must not expose mentor data from other chapters even in error messages

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Model the validation result as a sealed class or a simple data class with a list of errors — an empty errors list means valid. Run all Supabase lookups in parallel using `Future.wait()` for the independent checks (mentor status, chapter membership) and then a single duplicate check query. The chapter membership check should use the coordinator's JWT claims as the authoritative source of chapter_id rather than accepting it as a function parameter to eliminate any possibility of chapter spoofing. Define all error codes as a Dart enum or string constants class so the UI can switch on them for localized display.

This service should be registered as a Riverpod Provider (not a singleton) so it can be cleanly overridden in tests.

Testing Requirements

Unit tests with mocked repository covering all validation rules independently: active mentor passes, paused mentor returns MENTOR_PAUSED, mentor from different chapter returns MENTOR_CHAPTER_MISMATCH, future date returns INVALID_DATE, past date beyond 90 days returns DATE_OUT_OF_RANGE, duplicate record returns DUPLICATE_ACTIVITY. Test batch input of 5 mentors where 2 are invalid — confirm exactly 2 error results and 3 success results. Test empty input returns empty results without error. Integration test on Supabase staging verifying RLS prevents cross-chapter data access.

Component
Activity Attribution Validator
service medium
Epic Risks (3)
high impact high prob scope

Partial failures in bulk registration — where some mentors succeed and others fail — create a complex UX state that is easy to mishandle. If the UI does not clearly communicate which records succeeded and which failed, coordinators may re-submit already-saved records (creating duplicates) or miss failed records entirely (creating underreporting).

Mitigation & Contingency

Mitigation: Design the per-mentor result screen as a primary deliverable of this epic, not an afterthought. Use a clear list view with success/failure indicators per mentor name, and offer a 'Retry failed' action that pre-selects only the failed mentors for resubmission.

Contingency: If partial failure UX proves too complex to deliver within scope, implement a simpler all-or-nothing submission mode for the initial release with a clear error message listing which mentors failed, and defer the partial-retry UI to a follow-up sprint.

medium impact medium prob technical

Submitting proxy records for a large group (e.g., 30+ mentors) as individual Supabase inserts may cause latency issues or hit rate limits, degrading the coordinator experience and potentially causing timeout failures that leave data in an inconsistent state.

Mitigation & Contingency

Mitigation: Implement the BulkRegistrationOrchestrator to batch inserts using a Supabase RPC call that accepts an array of proxy records, reducing round-trips to a single network call. Add progress indication using a stream of per-record results if the RPC supports it.

Contingency: If the RPC approach is blocked by Supabase limitations, fall back to chunked parallel inserts (5 records per batch) with retry logic, capping total submission time and surface a progress bar to manage coordinator expectations.

medium impact medium prob technical

Unifying state management for both single and bulk proxy flows in a single BLoC risks state leakage between flows — for example, a previously selected mentor list persisting when a coordinator switches from bulk to single mode — causing confusing UI states or incorrect submissions.

Mitigation & Contingency

Mitigation: Define separate, named state subtrees within the BLoC for single-proxy state and bulk-proxy state, with explicit reset events triggered on flow entry. Write unit tests for state isolation scenarios using the bloc_test package.

Contingency: If unified BLoC state becomes unmanageable, split into two separate BLoCs (ProxySingleRegistrationBLoC and ProxyBulkRegistrationBLoC) sharing only common events via a parent coordinator Cubit.