high priority low complexity infrastructure pending backend specialist Tier 1

Acceptance Criteria

ActivitySaveMiddleware (or equivalent interceptor) is implemented as a composable layer that wraps ActivityRepository.save() without modifying its public interface
When `duplicateReviewed: true` is passed in the save context, the middleware sets `duplicate_reviewed = true` and `duplicate_reviewed_at = now()` on the activity record before/during the Supabase upsert
When `duplicateReviewed` is absent or false, the middleware does NOT set the flag, preserving normal save behavior
DuplicateCheckRepository.checkForDuplicates() skips activities where `duplicate_reviewed = true` — verified by unit test with a pre-reviewed activity in the dataset
The middleware is transparent: callers that do not pass duplicateReviewed context observe identical behavior to the pre-middleware save path
Supabase RLS policy is updated to prevent a peer mentor from self-setting duplicate_reviewed = true — only coordinator role or system-level upsert may set this flag
Integration test verifies that a save with `duplicateReviewed: true` followed by a checkForDuplicates call for the same activity does NOT return that activity as a candidate
Middleware does not introduce additional Supabase round-trips — the flag is set within the same upsert operation, not a separate UPDATE

Technical Requirements

frameworks
Flutter
Riverpod
Supabase
apis
Supabase PostgREST activities table (upsert with duplicate_reviewed column)
Supabase RLS policies
data models
Activity
ActivitySaveContext
performance requirements
Middleware must not add measurable latency beyond a single Supabase upsert — no additional network calls
Flag column (duplicate_reviewed) must be indexed if used in WHERE clauses of duplicate check queries
security requirements
RLS policy: `duplicate_reviewed` column is writable only by users with coordinator role or via Supabase service-role function — peer mentors cannot self-approve their own duplicates
The flag must be validated server-side via RLS, not just client-side logic, to prevent bypass
Audit trail: duplicate_reviewed_at timestamp and reviewed_by_user_id should be stored for compliance with NHF's Bufdir reporting requirements

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement the middleware as a Dart class following the decorator pattern: `DuplicateReviewedMiddleware implements ActivityRepository` wraps a delegate `ActivityRepository` and adds the flag logic before delegating to the wrapped save. This keeps the middleware testable in isolation without requiring a full BLoC stack. The ActivitySaveContext should be a value object (not a map) carrying typed optional fields including `duplicateReviewed: bool?`. Pitfall: using a separate UPDATE after the initial INSERT to set the flag creates a race condition if the activity is fetched between the two operations — always bundle the flag in the original upsert.

For the RLS policy, use a PostgreSQL column-level security check or a CHECK constraint combined with a role condition: `CHECK (duplicate_reviewed = false OR current_setting('role') = 'coordinator')`. Ensure the `duplicate_reviewed_at` column defaults to NULL (not a timestamp) so that unflagged activities are distinguishable from a flag set at epoch.

Testing Requirements

Unit tests using flutter_test: (1) save with duplicateReviewed=true includes duplicate_reviewed=true and duplicate_reviewed_at in the upsert payload; (2) save without duplicateReviewed context sends payload without the flag fields (no null overwrite); (3) ActivitySaveContext is correctly threaded through BLoC → repository → middleware without losing type safety; (4) middleware composes correctly when stacked with other hypothetical middleware (e.g., audit logging). Integration test against local Supabase: (1) insert an activity with duplicate_reviewed=true; (2) call check_activity_duplicates for the same peer_mentor + date + type; (3) assert the reviewed activity is excluded from results. RLS test: attempt to set duplicate_reviewed=true as a peer_mentor role user and assert PostgrestException with code 42501 (insufficient privilege).

Component
Duplicate Detection BLoC
infrastructure medium
Epic Risks (2)
medium impact high prob technical

For bulk registration with many participants, running duplicate checks sequentially before surfacing the consolidated summary could introduce a multi-second delay as each peer mentor is checked individually against the RPC. This degrades the bulk submission UX significantly.

Mitigation & Contingency

Mitigation: Issue all duplicate check RPC calls concurrently using Dart's `Future.wait` or a bounded parallel executor (max 5 concurrent calls to avoid Supabase rate limits). The BLoC collects all results and emits a single BulkDuplicateSummary state with the consolidated list.

Contingency: If concurrent RPC calls hit Supabase connection limits or rate limits, implement a batched sequential approach with a progress indicator showing 'Checking participant N of M' so the coordinator understands the delay is expected and bounded.

high impact medium prob integration

In proxy registration, the peer mentor's ID must be used as the duplicate check parameter, not the coordinator's ID. If the proxy context is not correctly threaded through the BLoC and service layer, duplicate checks will silently run against the wrong person, missing actual duplicates.

Mitigation & Contingency

Mitigation: Define a `SubmissionContext` model that carries the effective `peer_mentor_id` (distinct from `submitter_id`) and pass it explicitly through the BLoC event payload. The DuplicateDetectionService always reads peer_mentor_id from SubmissionContext, never from the authenticated user session.

Contingency: If SubmissionContext threading proves difficult to retrofit into the existing proxy registration BLoC, add an assertion in DuplicateDetectionService that throws a descriptive error when peer_mentor_id is null or matches the coordinator's own ID in a proxy context, making the bug immediately visible in testing.