Implement Duplicate Resolution Handler
epic-duplicate-activity-detection-state-management-task-007 — Create the DuplicateResolutionHandler service that processes user decisions: KEEP_BOTH (proceed with save, flag as reviewed), CANCEL (abort current activity), or REPLACE (delete candidate, save new). Emit structured audit trail events for each decision path. Integrate with the duplicate-reviewed-flag middleware to prevent re-detection. Depends on DuplicateCheckRepository and DuplicateDetectionService.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Use a Supabase RPC (Postgres function) for the REPLACE path to ensure atomicity — DELETE + INSERT in a single server-side transaction is safer than two sequential client calls. The duplicate-reviewed-flag should be a boolean column 'duplicate_reviewed' on the activity table, defaulting to false, set to true server-side inside the RPC for KEEP_BOTH and REPLACE. The CANCEL path is entirely client-side: emit the audit event via a separate lightweight Supabase insert, then signal the BLoC to transition to 'cancelled' state — no activity row is created. Model ResolutionResult as a Dart sealed class for exhaustive pattern matching in the BLoC.
Register via Riverpod and ensure it is disposed with the BLoC. For audit events, reuse the claim_event table structure (actor_id, actor_role, from_status, to_status) with an additional resolution_type field.
Testing Requirements
Unit tests: test each resolution path (KEEP_BOTH, CANCEL, REPLACE) with mocked repositories. Test REPLACE rolls back new activity insert if candidate delete fails. Test that the duplicate-reviewed-flag is correctly set on KEEP_BOTH path. Test REPLACE with already-deleted candidate returns ErrorResult without throwing.
Test audit event shape matches required schema for all three paths. Integration tests: verify REPLACE atomicity — run against a real Supabase test instance and confirm no partial writes. Verify cross-organization deletion is blocked by RLS. Confirm audit log entries are created for all resolution types.
Achieve 90% branch coverage for the handler.
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.
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.