high priority medium complexity integration pending integration specialist Tier 7

Acceptance Criteria

When a new claim is submitted by a peer mentor in the coordinator's chapter, it appears in the coordinator's queue within 3 seconds without any manual action
When a claim is approved or rejected by another coordinator session (same account, two devices), the claim disappears from the queue on both devices within 3 seconds
Subscription is filtered server-side to the coordinator's chapter_id so events from other chapters are never delivered to the client
On chapter scope change (coordinator switches chapter if multi-chapter support applies), the previous subscription is cleanly unsubscribed before the new one is established
On app backgrounding followed by foreground restore, the subscription reconnects automatically and a reconciliation fetch is triggered to cover any events missed while backgrounded
Incoming INSERT event merges the new PendingClaim at the correct sorted position (submittedAt ascending) rather than appending to the end
Incoming UPDATE event with status != 'pending-attestation' removes the claim from the local list (it was decided elsewhere)
Subscription lifecycle is tied to the Riverpod provider lifecycle — disposing the provider cancels the subscription with no memory leak
RLS policies on Realtime channel enforce that only the coordinator's chapter events are delivered — cross-chapter events must be blocked at the server

Technical Requirements

frameworks
Flutter
Riverpod
Dart
apis
Supabase Realtime (WebSocket channel)
Supabase Auth (JWT for channel authentication)
data models
claim_event
performance requirements
Realtime event processing (merge into state) must complete in under 50ms on the Dart side to avoid frame drops
On reconnect after a network outage, a single reconciliation fetch replaces any event-based gap filling to avoid complex diff logic
Channel subscription must not block the initial queue fetch — establish subscription in parallel with the first page load
security requirements
RLS policy on Realtime must validate the coordinator's JWT claims for chapter scope — subscription without valid JWT must be rejected by the server
No sensitive PII transmitted in Realtime payloads: payload contains only claim_id and status; full claim details are fetched from REST on INSERT events
JWT token refreshed before resubscription to ensure channel is never authenticated with an expired token

Execution Context

Execution Tier
Tier 7

Tier 7 - 84 tasks

Can start after Tier 6 completes

Implementation Notes

Use supabase.channel('attestation-queue-{chapterId}').onPostgresChanges(event: PostgresChangeEvent.all, schema: 'public', table: 'expense_claims', filter: PostgresChangeFilter(type: FilterType.eq, column: 'chapter_id', value: chapterId), callback: _handleRealtimeEvent).subscribe(). In _handleRealtimeEvent, for INSERT events fetch the full PendingClaim details via a single REST call (the Realtime payload only contains raw columns, not joined claimant name). For UPDATE events check the new.status field: if not 'pending-attestation', remove from local list. Override ref.onDispose() in the AsyncNotifier to call _channel.unsubscribe().

For the backgrounding/foreground reconciliation, use WidgetsBindingObserver in a separate provider that watches AppLifecycleState.resumed and calls ref.invalidate(attestationQueueProvider). Avoid storing the full PendingClaim in Realtime payloads — this keeps payload size minimal and avoids transmitting PII over the WebSocket.

Testing Requirements

Unit tests with a fake RealtimeChannel that emits synthetic payload events: (1) INSERT payload → new PendingClaim fetched and inserted at correct sort position; (2) UPDATE with status='approved' → claim removed from list; (3) UPDATE with status='pending-attestation' (re-opened) → claim added back; (4) channel disconnect → isConnected flag set false, reconciliation fetch triggered on reconnect; (5) provider disposal → channel.unsubscribe() called exactly once. Use flutter_test. Integration test on a staging Supabase project: open two Dart isolates, submit a claim from isolate B, verify isolate A's notifier receives the INSERT event and queue length increases by 1 within 5 seconds.

Component
Expense Attestation Service
service medium
Epic Risks (3)
high impact medium prob scope

Mutual exclusion rules are stored in the expense type catalogue's exclusive_groups field. If the catalogue schema or group definitions differ between HLF and Blindeforbundet, the validation service must handle multiple group configurations without hardcoding organisation-specific logic.

Mitigation & Contingency

Mitigation: Design the validation service to be purely data-driven: it reads exclusive_groups from the cached catalogue and enforces whichever groups are defined, with no hardcoded organisation names. Write parameterised unit tests covering at least 4 different catalogue configurations to verify generality.

Contingency: If an organisation requires non-standard exclusion semantics (e.g. partial exclusion within a group), introduce an exclusion_type field to the catalogue schema and extend the service, treating it as a catalogue configuration change rather than a code fork.

medium impact high prob technical

The attestation service subscribes to Supabase Realtime for live queue updates. On mobile, Realtime WebSocket connections can be dropped during network transitions, causing the coordinator queue to become stale without the user being aware.

Mitigation & Contingency

Mitigation: Implement connection lifecycle management: reconnect on network-change events, show a 'reconnecting' indicator when the subscription is broken, and perform a full queue refresh on reconnect rather than relying solely on delta events.

Contingency: Add a manual pull-to-refresh gesture on the attestation queue screen as a guaranteed fallback. If Realtime proves unreliable in production, switch to periodic polling (30-second interval) as a degraded but functional mode.

medium impact medium prob integration

If a peer mentor submits a draft while offline and then submits the same claim again after connectivity is restored (thinking the first attempt failed), duplicate claims may be persisted in Supabase.

Mitigation & Contingency

Mitigation: Assign a client-generated idempotency key (UUID) to each draft at creation time. The submission service sends this key as an upsert key to Supabase, preventing duplicate inserts. The draft is marked 'submitted' locally after first successful upload.

Contingency: Implement a server-side duplicate detection trigger on the expense_claims table checking (activity_id, claimant_id, created_date) within a 24-hour window and returning the existing record ID rather than inserting a duplicate.