critical priority medium complexity integration pending backend specialist Tier 3

Acceptance Criteria

AttributionService.register() calls DuplicateDetectionService.check(mentorId, date, activityTypeId) before writing any attribution fields
When DuplicateDetectionService returns one or more conflicts, AttributionService throws a DuplicateBlockedError containing the full ConflictMetadata list (conflicting activity IDs, registered_by IDs, timestamps)
When no duplicate is detected, AttributionService proceeds to enforce registered_by and attributed_to fields without alteration
DuplicateBlockedError is a typed Dart class (not a generic Exception) with fields: conflictingActivityIds, mentorId, date, activityTypeId
Batch attribution path also performs per-record duplicate pre-checks and aggregates DuplicateBlockedError instances into BatchPartialFailureResult
No activity row is inserted or modified when a duplicate is detected — the operation is fully aborted
Integration does not introduce circular dependencies between the two service classes
The combined enforcement layer is exposed via a single facade method usable by higher-level proxy-registration-service

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Supabase PostgreSQL 15
Supabase Auth
data models
activity
activity_type
performance requirements
Duplicate check + attribution enforcement must complete within 300ms for single-record path under normal network conditions
Batch path must not issue more than one duplicate-check query per record (no N+1 on detection)
security requirements
registered_by must always be sourced from the authenticated JWT subject — never from caller-supplied payload
attributed_to must be validated as a real peer_mentor user ID in the same organization before acceptance
DuplicateBlockedError must not expose PII beyond IDs in its payload

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Dependents (28)
epic-proxy-activity-registration-orchestration-task-001 component Cross-Epic Component proxy-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-orchestration-task-002 component Cross-Epic Component proxy-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-orchestration-task-003 component Cross-Epic Component proxy-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-orchestration-task-004 component Cross-Epic Component proxy-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-orchestration-task-005 component Cross-Epic Component proxy-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-orchestration-task-006 component Cross-Epic Component bulk-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-orchestration-task-007 component Cross-Epic Component bulk-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-orchestration-task-008 component Cross-Epic Component bulk-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-orchestration-task-009 component Cross-Epic Component bulk-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-orchestration-task-010 component Cross-Epic Component proxy-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-orchestration-task-011 component Cross-Epic Component bulk-registration-service depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-001 component Cross-Epic Component bulk-participant-list depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-002 component Cross-Epic Component bulk-participant-list depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-003 component Cross-Epic Component bulk-participant-list depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-004 component Cross-Epic Component duplicate-warning-dialog depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-005 component Cross-Epic Component duplicate-warning-dialog depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-006 component Cross-Epic Component duplicate-warning-dialog depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-007 component Cross-Epic Component proxy-peer-mentor-selector depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-008 component Cross-Epic Component proxy-peer-mentor-selector depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-009 component Cross-Epic Component proxy-peer-mentor-selector depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-010 component Cross-Epic Component proxy-activity-form depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-011 component Cross-Epic Component proxy-activity-form depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-012 component Cross-Epic Component proxy-registration-screen depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-013 component Cross-Epic Component proxy-registration-screen depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-014 component Cross-Epic Component proxy-registration-screen depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-015 component Cross-Epic Component bulk-registration-screen depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-016 component Cross-Epic Component bulk-registration-screen depends on activity-attribution-service
epic-proxy-activity-registration-ui-task-017 component Cross-Epic Component bulk-registration-screen depends on activity-attribution-service

Implementation Notes

Inject DuplicateDetectionService into AttributionService via constructor (Riverpod Provider). The check must happen before any field mutation so attribution fields are never partially written. Define DuplicateBlockedError as a sealed class or final class in a shared domain errors file to allow exhaustive pattern matching in callers. For batch flow, use Future.wait with individual try/catch per record to collect partial failures without aborting the entire batch prematurely.

Avoid direct Supabase calls inside AttributionService — delegate persistence to the repository layer to keep services testable without network.

Testing Requirements

Unit tests covering: (1) duplicate detected → DuplicateBlockedError thrown with correct metadata, (2) no duplicate → attribution proceeds and returns enriched record, (3) batch with partial duplicates → BatchPartialFailureResult lists per-record outcomes, (4) DuplicateDetectionService throws unexpected error → AttributionService propagates a wrapped ServiceUnavailableError (not swallowed). Mock DuplicateDetectionService to isolate attribution logic. Verify DuplicateDetectionService.check() is called exactly once per record in single and batch flows.

Component
Activity Attribution Service
service low
Epic Risks (2)
medium impact medium prob scope

Overly strict duplicate matching (exact date + type) may flag legitimate back-to-back sessions of the same activity type on the same day as duplicates, frustrating coordinators and undermining trust in the feature.

Mitigation & Contingency

Mitigation: Confirm with product owners whether the matching key should be (mentor_id, date, activity_type_id) only or should also consider duration and time-of-day. Document the chosen threshold in the service and surface it in the duplicate warning dialog for transparency.

Contingency: If false-positive rates are high in user testing, add a duration-window tolerance parameter to the detection query that can be tuned without code changes.

high impact low prob security

If the current session token is invalidated between the coordinator starting the proxy form and submitting it, the activity-attribution-service may fail to resolve the coordinator's user ID, causing a silent attribution error.

Mitigation & Contingency

Mitigation: Read the coordinator's user ID from the session at service call time rather than at form-open time. Validate the session is still active before committing the insert, and surface a clear re-authentication prompt if it has expired.

Contingency: If a mis-attributed record is detected post-submission, the audit log retains the original session metadata, allowing a corrective record to be created with accurate attribution.