high priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

validateDeepLinkArgs(context) queries Supabase to confirm activityId exists in the activities table and the row's user_id matches the authenticated user's ID
validateDeepLinkArgs(context) queries Supabase to confirm contactId exists and is accessible to the current user (RLS-enforced, no manual auth check needed)
validateDeepLinkArgs(context) checks that prefillDate is not more than 90 days in the past and not in the future
validateDeepLinkArgs(context) checks that prefillDuration is between 1 and 480 minutes (inclusive)
validateDeepLinkArgs(context) returns a DeepLinkValidationResult with isValid=true and all validated fields when all checks pass
validateDeepLinkArgs(context) returns DeepLinkValidationResult with isValid=false and a failureReason enum value when any check fails
navigateToWizard() calls validateDeepLinkArgs() before routing; on failure it calls fallbackToActivityList(router, reason) instead
fallbackToActivityList() navigates to the activity list screen using router.go() and schedules a toast notification with a user-friendly message corresponding to the failureReason
Toast messages are localized and do not expose internal IDs, database error details, or stack traces to the user
Validation failures are logged to the app's analytics/observability system with the failureReason and context metadata (no PII)
The entire validate-then-navigate flow completes within 1500ms under normal network conditions
If the Supabase validation query itself fails due to a network error, the fallback route is also taken with failureReason=NETWORK_ERROR

Technical Requirements

frameworks
Flutter
go_router
Riverpod
flutter_test
apis
Supabase REST API (activities table, contacts table)
Flutter SnackBar or equivalent toast API
data models
DeepLinkValidationResult (isValid: bool, failureReason: DeepLinkFailureReason?, validatedContext: ScenarioContext?)
DeepLinkFailureReason (enum: ACTIVITY_NOT_FOUND, ACTIVITY_NOT_OWNED, CONTACT_NOT_ACCESSIBLE, PREFILL_DATE_OUT_OF_RANGE, PREFILL_DURATION_OUT_OF_RANGE, NETWORK_ERROR)
performance requirements
validateDeepLinkArgs() must complete within 800ms including two Supabase round-trips (batch the two ID lookups into a single query or use parallel Future.wait where possible)
Fallback navigation must be immediate — no spinner or loading state shown to the user
security requirements
All Supabase queries in validation must rely on RLS policies for access control — never pass user_id as a query filter in client code as the sole security mechanism
DeepLinkFailureReason.ACTIVITY_NOT_OWNED must not be distinguishable from ACTIVITY_NOT_FOUND in the user-facing toast message to prevent enumeration attacks
Analytics logging of validation failures must strip contactId and activityId and log only the failureReason and activityType
ui components
SnackBar (contextual toast)
Activity list screen (navigation target)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Add validateDeepLinkArgs(ScenarioContext context) as an async method on ScenarioDeepLinkHandler that returns Future. Use Future.wait([activityQuery, contactQuery]) to run the two Supabase lookups in parallel rather than sequentially, halving the network latency. For the prefillDate range check, use a clock abstraction (inject a DateTime Function() now parameter) so tests can control the current time. For fallbackToActivityList(), use a GlobalKey or the ScaffoldMessenger.of(context) pattern to show the SnackBar; prefer the GlobalKey approach since navigation may have already changed the BuildContext by the time the fallback is called.

Map each DeepLinkFailureReason to a localized string via a dedicated DeepLinkErrorMessages class using the app's existing localization infrastructure. Never show raw exception messages to the user.

Testing Requirements

Write unit tests using flutter_test with mocked Supabase repository. Test cases: (1) all fields valid → isValid=true, (2) activityId not found in DB → ACTIVITY_NOT_FOUND, (3) activityId found but wrong user_id → ACTIVITY_NOT_OWNED (verify the user-facing message is identical to ACTIVITY_NOT_FOUND for security), (4) contactId not accessible → CONTACT_NOT_ACCESSIBLE, (5) prefillDate 91 days in the past → PREFILL_DATE_OUT_OF_RANGE, (6) prefillDate in the future → PREFILL_DATE_OUT_OF_RANGE, (7) prefillDuration = 0 → PREFILL_DURATION_OUT_OF_RANGE, (8) prefillDuration = 481 → PREFILL_DURATION_OUT_OF_RANGE, (9) Supabase throws exception → NETWORK_ERROR. Widget test: verify that fallbackToActivityList() triggers a SnackBar with non-empty text and that router.go() was called with the activity list route. Verify analytics mock received a call with failureReason and no PII.

Component
Scenario Deep Link Handler
service medium
Epic Risks (2)
high impact medium prob scope

The Rule Engine must support a flexible JSON rule schema that can express compound conditions (e.g., contact_type AND wellbeing_flag AND delay_days). Underestimating schema expressiveness may require breaking changes to the rule format after coordinators have already configured rules.

Mitigation & Contingency

Mitigation: Define and freeze the rule JSON schema (trigger_type enum, metadata_conditions structure, delay logic) before any implementation begins; validate schema against all known HLF scenarios documented in the feature spec.

Contingency: If schema changes are needed after deployment, implement a schema version field and a migration utility that upgrades stored rules to the new format without coordinator intervention.

medium impact medium prob technical

Deep-link navigation to the activity wizard with pre-filled arguments may fail if the user's session has expired or if the wizard route is not yet mounted in the navigator stack, causing unhandled navigation exceptions.

Mitigation & Contingency

Mitigation: Implement session state check before navigation; if session is expired, redirect to biometric/login screen and store the pending deep-link URI for post-auth redirect using go_router's redirect mechanism.

Contingency: If post-auth redirect proves unreliable, fall back to navigating to the home screen with a visible action banner that re-triggers the wizard with pre-filled arguments.