critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

ActivityAttributionValidator is a stateless class injectable via Riverpod, accepting a ProxyActivityRequest and acting coordinator ID, returning a Future<ValidationResult>
ValidationResult is a sealed class with ValidationResult.valid() and ValidationResult.invalid(List<ValidationFailureReason> reasons) variants; multiple rule failures are returned together rather than failing on first violation
Rule 1 — Self-attribution guard: if ProxyActivityRequest.attributedMentorId equals actingCoordinatorId, ValidationFailureReason.selfAttribution is included in the failure list
Rule 2 — Scope check: the validator queries the members/user_roles table to confirm attributedMentorId has an active peer mentor role within any chapter or unit the coordinator manages; if not found or inactive, ValidationFailureReason.mentorOutOfScope or ValidationFailureReason.mentorInactive is returned respectively
Rule 3 — Duplicate detection: the validator queries proxy_activities for an existing record matching (attributedMentorId, activityType) within a configurable date window (default: same calendar day UTC); if found, ValidationFailureReason is mapped to a DuplicateDetected result at the service layer (validator returns ValidationFailureReason.duplicateDetected)
The configurable duplicate detection window is injected via a DuplicateDetectionConfig value object (windowStart: DateTime, windowEnd: DateTime), defaulting to (startOfDay, endOfDay) for the request date when not provided
All three rules are evaluated independently and failures are aggregated; a single request can fail all three rules simultaneously
Supabase query errors in rule 2 or rule 3 are caught and surfaced as ValidationResult.invalid([ValidationFailureReason.validationQueryFailed]) with the error message preserved for logging
Rule 1 (self-attribution) is evaluated in-memory before any network calls to fast-fail without a Supabase round-trip
Unit tests cover: all three rules pass (valid result), each rule fails independently, multiple rules fail simultaneously, Supabase error in scope check, Supabase error in duplicate check, null/empty mentor ID input

Technical Requirements

frameworks
Dart
Riverpod
apis
Supabase REST API — query members/user_roles table for mentor scope check
Supabase REST API — query proxy_activities table for duplicate detection
data models
ProxyActivityRequest
ValidationResult (sealed)
ValidationFailureReason (enum)
DuplicateDetectionConfig
CoordinatorScopeRecord (internal DTO for scope query result)
performance requirements
Rule 1 must execute in-memory with zero latency before triggering any Supabase queries
Rules 2 and 3 should fire concurrently using Future.wait when rule 1 passes, to minimise total validation latency
Scope check query must use a Supabase index on (coordinator_id, chapter_id) to avoid full table scans; ensure the index exists or create a migration
Total validation round-trip (rules 2 + 3 concurrent) must complete within 1 second on a standard mobile connection
security requirements
Scope check must be enforced server-side via Supabase RLS in addition to the client-side validator — the validator is a UX guard, not a security boundary
Duplicate detection query must filter by acting_coordinator_id in addition to mentor and activity type to prevent cross-coordinator data leakage in the query result set
ValidationResult must never include raw database row data — only typed reasons are surfaced to callers

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Structure the validator with three private methods: _checkSelfAttribution (synchronous), _checkMentorScope (async, Supabase query), _checkDuplicate (async, Supabase query). Call _checkSelfAttribution first; if it fails, still proceed to _checkMentorScope and _checkDuplicate concurrently via Future.wait to collect all failures in one pass. Use Riverpod Provider to inject both a ScopeRepository and a ProxyActivityRepository (or their Supabase implementations) into the validator — never instantiate Supabase client directly inside the validator. The DuplicateDetectionConfig should default to windowStart = DateTime(date.year, date.month, date.day, 0, 0, 0).toUtc() and windowEnd = DateTime(date.year, date.month, date.day, 23, 59, 59).toUtc() computed from the request's activityDate.

Add a brief doc comment on the class explaining the dual-layer security model (client-side UX guard + server-side RLS).

Testing Requirements

Write unit tests using flutter_test with a mocked Supabase client (or a mock ProxyActivityRepository and mock ScopeRepository). Required test cases: (1) all rules pass → ValidationResult.valid(), (2) self-attribution alone fails, (3) mentor out of scope alone fails, (4) mentor inactive alone fails, (5) duplicate detected alone fails, (6) self-attribution + out-of-scope both fail in same result, (7) scope query throws SupabaseException → ValidationResult.invalid([validationQueryFailed]), (8) duplicate query throws SupabaseException → ValidationResult.invalid([validationQueryFailed]), (9) rule 1 fails → rules 2 and 3 are still evaluated (no early exit). Verify via call count mocks that rules 2 and 3 are called concurrently (both initiated before either awaits). Minimum 90% branch coverage.

Component
Activity Attribution Validator
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.