high priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

When fee validation returns RequiresApproval, the service creates an expense claim record in Supabase with status PENDING_ATTESTATION before returning to the caller
The created expense claim record contains: assignment_id (FK), org_id, fee_amount_ore, requested_by (coordinator user_id), created_at (UTC), status = PENDING_ATTESTATION
The service invokes ExpenseAttestationService.requestAttestation(claimId) after persisting the claim record
The assignment record is written with status AWAITING_EXPENSE_APPROVAL — it is never set to CONFIRMED until the claim transitions to APPROVED
If claim creation succeeds but ExpenseAttestationService.requestAttestation() throws, the assignment remains in AWAITING_EXPENSE_APPROVAL and the error is surfaced to the caller — no silent failure
If claim creation fails (Supabase error), no assignment record is written and the caller receives an AssignmentCreationException
A subsequent call to confirmAssignment() on an assignment in AWAITING_EXPENSE_APPROVAL status throws AssignmentNotReadyException with a message indicating the pending claim ID
When the claim transitions to APPROVED (handled by the attestation service), a domain event or callback updates the assignment status to CONFIRMED
When the claim transitions to REJECTED, the assignment transitions to EXPENSE_REJECTED and the coordinator is notified
The entire create-claim + request-attestation sequence is wrapped in a Supabase transaction or compensating transaction to ensure no orphaned claims

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
flutter_test
apis
Supabase PostgREST REST API (expense_claims table insert, driver_assignments table insert/update)
ExpenseAttestationService internal interface (requestAttestation)
data models
DriverAssignment (status enum: PENDING, AWAITING_EXPENSE_APPROVAL, CONFIRMED, EXPENSE_REJECTED)
ExpenseClaim (id, assignment_id, org_id, fee_amount_ore, requested_by, status, created_at)
ExpenseClaimStatus (enum: PENDING_ATTESTATION, APPROVED, REJECTED)
performance requirements
Claim creation and attestation invocation must complete within one Supabase round-trip plus one internal service call — no polling loops in this method
Supabase insert for expense_claims must use returning=minimal to avoid over-fetching
security requirements
RLS on expense_claims must enforce that only users with coordinator or admin role within the same org_id can insert records
assignment_id FK constraint must be validated at the database level to prevent orphaned claims
Fee amount in the claim must be immutable after insert — implement via Supabase policy denying UPDATE on fee_amount_ore

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use a repository pattern: DriverAssignmentRepository and ExpenseClaimRepository are abstract interfaces injected via Riverpod. The DriverAssignmentService orchestrates them without direct Supabase client calls — this keeps the service unit-testable. For the compensating transaction: Supabase does not support multi-table transactions in PostgREST directly; use a Supabase Edge Function (Postgres function wrapped in a transaction) for the atomic claim + assignment insert. Define a domain event AssignmentExpenseStatusChanged that the BLoC layer listens to for UI updates.

Store the pending claim ID on the assignment record (pending_expense_claim_id nullable FK) so the UI can deep-link to the claim detail. Avoid polling for claim status in this service — use Supabase Realtime subscription on the expense_claims row to react to status changes.

Testing Requirements

Unit tests (flutter_test) with mocked Supabase client and mocked ExpenseAttestationService covering: happy path (claim created, attestation invoked, assignment in AWAITING_EXPENSE_APPROVAL), attestation service throws after claim is created (assignment stays AWAITING, error propagated), Supabase insert fails (no assignment written, exception thrown), confirmAssignment called on AWAITING assignment (throws AssignmentNotReadyException). Integration test: run the full flow against the Supabase test project and verify database row states at each step. Verify RLS prevents a coordinator from a different org inserting a claim.

Component
Driver Assignment Service
service medium
Epic Risks (4)
high impact medium prob security

Org-scoped encryption key management is complex. If keys are not correctly isolated per organization, a breach in one org's key could expose another org's declarations. Additionally, key rotation is not specified but may be needed for compliance, and the current implementation may not support it.

Mitigation & Contingency

Mitigation: Use Supabase Vault or a dedicated secrets management approach for org-scoped key storage. Define the key derivation strategy (per-org master key) in a security design document reviewed before implementation begins. Include key isolation tests in the test suite.

Contingency: If a full per-org key management system cannot be safely implemented within the sprint, fall back to a single platform-level encryption key with strict RLS isolation as a temporary measure, flagging the key rotation gap as a security debt item with a defined resolution milestone.

medium impact medium prob integration

Push notification delivery to drivers depends on FCM token availability and device connectivity. If a driver has not granted notification permissions or has an expired FCM token, the declaration delivery notification will silently fail, leaving the coordinator unaware and the declaration unacknowledged.

Mitigation & Contingency

Mitigation: Implement delivery status tracking in declaration-notification-service. Fall back to in-app notification and SMS (if configured) when push delivery fails. Expose delivery failure status in the declaration status badge so coordinators can identify and manually follow up.

Contingency: If push delivery proves unreliable, implement a polling-based in-app notification fallback where drivers see pending declarations on next app open, ensuring the workflow can complete even without push notifications.

medium impact medium prob technical

The acknowledgement service is meant to validate that the driver has fully scrolled through the declaration before confirming. Implementing reliable scroll completion detection in Flutter across different screen sizes and font sizes is technically non-trivial and could be bypassed.

Mitigation & Contingency

Mitigation: Implement scroll position tracking using ScrollController with a threshold (e.g., 95% of content height reached) and record the validated state server-side before allowing acknowledgement submission. Document the approach in the legal sign-off checkpoint noted in the feature documentation.

Contingency: If reliable scroll detection cannot be implemented within the sprint, add a mandatory reading delay timer (e.g., estimated reading time based on word count) as an alternative validation mechanism, pending legal review of the approach.

medium impact low prob dependency

The driver assignment service must coordinate with the threshold-based expense approval workflow for fees above configured thresholds. If the expense approval workflow interface changes or is not yet stable, the integration point could break or produce incorrect routing behavior.

Mitigation & Contingency

Mitigation: Define a clear interface contract between driver-assignment-service and the expense approval workflow before implementation. Use dependency injection so the expense workflow client can be mocked in tests. Monitor the expense approval feature for interface changes.

Contingency: If the expense approval workflow interface is not stable, implement a direct database insert to the expense records table as a temporary bypass, with a flag indicating manual review is needed, until the stable interface is available.