critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

ReferralAttributionService class exists in the codebase and compiles without errors or warnings
The class is exposed as a Riverpod Provider (or NotifierProvider if stateful) and can be read via ref.watch / ref.read in any widget or other provider
Constructor accepts RecruitmentAttributionRepository and BadgeCriteriaIntegration as required positional or named parameters
All five public methods are declared with correct return types and parameter signatures: recordClickEvent, matchRegistrationToCode, confirmAttribution, getAttributionCountsForMentor, publishMilestoneEvent
Method bodies throw UnimplementedError with a descriptive message (e.g., 'Implemented in task-006') as stubs until subsequent tasks fill them in
Provider override for testing is possible: a test can supply a fake RecruitmentAttributionRepository without modifying production code
A smoke test (providerContainer.read(referralAttributionServiceProvider)) succeeds without runtime exception when fake dependencies are injected
No circular dependencies exist between this provider and its injected abstractions

Technical Requirements

frameworks
Flutter
Riverpod
Dart
data models
AttributionRecord
ClickEvent
MilestoneEvent
AttributionCounts
performance requirements
Provider instantiation must be synchronous; no async work in the constructor or provider factory
security requirements
Repository and integration dependencies must be injected as abstract interfaces, not concrete classes, to prevent tight coupling and allow secure test overrides

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Riverpod's `Provider` (or `NotifierProvider` if you anticipate exposing reactive state). Prefer constructor injection over `ref.read` inside the service to keep the class testable outside Riverpod. Define abstract interfaces `IRecruitmentAttributionRepository` and `IBadgeCriteriaIntegration` if they do not exist yet — the service should depend on the abstractions, not the Supabase-backed implementations. Follow the same provider file structure used by ReferralCodeService (task-001).

Place the provider definition in `lib/features/recruitment/services/referral_attribution_service.dart`. Export the provider constant from the feature barrel file. Use `@visibleForTesting` annotation on any internal helpers that tests need to access.

Testing Requirements

Write a single flutter_test that creates a ProviderContainer, overrides referralAttributionServiceProvider with a fake repository and fake BadgeCriteriaIntegration, reads the provider, and asserts the returned object is a ReferralAttributionService instance. Each stub method should throw UnimplementedError; assert that calling one throws. This test validates the scaffolding wiring before any logic is implemented. Test file: test/services/referral_attribution_service_test.dart.

Component
Referral Attribution Service
service high
Epic Risks (3)
high impact medium prob integration

Confirmed registration events originate from the membership system (Dynamics portal for HLF), which may call back asynchronously with significant delay. If the attribution service only accepts synchronous confirmation at registration time, late callbacks will fail to match the originating referral code, resulting in under-counted conversions.

Mitigation & Contingency

Mitigation: Design the attribution confirmation path as a webhook endpoint (Supabase Edge Function) that accepts a referral_code + new_member_id pair at any time after click. The service matches by code string, not by session. Persist pending_signup events immediately at onboarding screen submission so there is always a record to upgrade to 'confirmed' when the webhook fires.

Contingency: If the membership system cannot reliably call the webhook, implement a polling reconciliation job (Supabase pg_cron, daily) that queries the membership system for recently registered members and back-fills any unmatched attribution records.

medium impact medium prob technical

If confirmRegistration() is called more than once for the same new member (e.g., idempotency retry from the webhook), duplicate milestone events could be emitted, causing the badge system to award badges multiple times.

Mitigation & Contingency

Mitigation: Use a UNIQUE constraint on (referral_code_id, new_member_id) in the referral_events table for confirmed events. The confirmRegistration() method uses upsert semantics; milestone evaluation reads the confirmed count from the aggregation query rather than counting individual calls.

Contingency: If duplicate awards occur in production, the badge system should support idempotent award checks (query existing badges before awarding). Add a deduplication guard in BadgeCriteriaIntegration as a secondary defence.

medium impact medium prob scope

Stakeholder review may expand attribution requirements mid-epic to include click-through tracking per channel (WhatsApp vs SMS vs email), which is not currently in scope but was mentioned in user story discussions. This would require schema changes in the foundation epic and delay delivery.

Mitigation & Contingency

Mitigation: Capture per-channel data in the device_metadata JSONB field from day one as an unstructured field (share_channel: 'whatsapp'). This preserves data without requiring a schema column, allowing structured querying to be added later without migrations.

Contingency: If channel-level analytics become a hard requirement during this epic, timebox the change to adding a nullable channel column to referral_events and a corresponding filter parameter on the aggregation query, deferring dashboard UI to a separate task.