critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

DeclarationRepository class exists in lib/features/declarations/data/repositories/ with a corresponding abstract interface (IDeclarationRepository) for dependency injection and testability
createDeclaration(driverId, templateVersionId, encryptedContent, orgId) → Future<Declaration> inserts a row with status='pending' and sent_at=now()
getDeclarationsByDriver(driverId) → Future<List<Declaration>> returns all non-soft-deleted declarations for the given driver, ordered by sent_at descending
acknowledgeDeclaration(declarationId) → Future<Declaration> sets status='acknowledged' and acknowledged_at=now(); throws if current status is not 'pending'
getDeclarationsByTemplateVersion(templateVersionId) → Future<List<Declaration>> returns all declarations referencing the given template version
softDeleteDeclaration(declarationId) → Future<void> sets deleted_at=now() and deleted_by=currentUserId; all other read methods exclude soft-deleted rows
All methods throw typed exceptions (DeclarationNotFoundException, DeclarationStatusTransitionException, DeclarationRepositoryException) rather than raw Supabase errors
Repository is registered in the Riverpod provider tree and injectable via ref.watch(declarationRepositoryProvider)
All public methods have corresponding unit tests with mocked Supabase client achieving ≥90% line coverage on the repository class
Status transition guard: acknowledgeDeclaration throws DeclarationStatusTransitionException if declaration is already 'acknowledged' or 'expired'

Technical Requirements

frameworks
Flutter
Riverpod (provider registration)
supabase_flutter (Supabase Dart client)
apis
Supabase PostgREST API (confidentiality_declarations table)
Supabase Auth (auth.currentUser for deleted_by population)
data models
Declaration (Dart model: id, orgId, driverId, templateVersionId, declarationContent, status, sentAt, acknowledgedAt, createdAt, deletedAt)
DeclarationStatus (enum: pending, acknowledged, expired)
performance requirements
getDeclarationsByDriver must return results in <500ms for drivers with up to 200 declarations
Supabase queries must use .select() with explicit column lists — avoid SELECT * to reduce payload size
security requirements
Repository must never expose decrypted declaration_content in logs or error messages
softDeleteDeclaration must only allow the authenticated coordinator or admin to set deleted_by — never a driver
All Supabase calls use the authenticated session token; never use service_role key in client-side Flutter code

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Follow the existing repository pattern in the codebase (abstract interface + concrete Supabase implementation). Use Riverpod's Provider or AsyncNotifierProvider for the repository, not BLoC directly — the BLoC layer sits above the repository. The Declaration Dart model should use freezed for immutability and copyWith support; add fromJson/toJson for Supabase response mapping. Status transition validation should be in the repository (not just the BLoC) because the repository is the single gatekeeper for persistence — the BLoC can also validate for UX, but the repository is the enforcer.

When building the soft-delete query, use .update({'deleted_at': DateTime.now().toIso8601String(), 'deleted_by': supabase.auth.currentUser!.id}).eq('id', declarationId) and always chain .is_('deleted_at', null) on read queries. Avoid exposing raw Supabase PostgrestException to callers — catch it in every public method and rethrow as a domain exception with a human-readable message.

Testing Requirements

Write unit tests using flutter_test with a mocked SupabaseClient (use mockito or mocktail). Test cases must cover: (1) createDeclaration returns a Declaration with status pending; (2) getDeclarationsByDriver excludes soft-deleted rows; (3) acknowledgeDeclaration succeeds when status is pending; (4) acknowledgeDeclaration throws DeclarationStatusTransitionException when status is acknowledged; (5) acknowledgeDeclaration throws DeclarationStatusTransitionException when status is expired; (6) softDeleteDeclaration sets deleted_at and deleted_by; (7) getDeclarationsByTemplateVersion returns correct subset; (8) network error from Supabase is wrapped in DeclarationRepositoryException. Write one Supabase integration test (against local emulator) verifying the full create→acknowledge lifecycle end-to-end.

Epic Risks (3)
high impact medium prob security

Row-level security policies for driver assignments and declarations must correctly scope data to the coordinator's chapter without leaking records across organizations. An incorrect RLS predicate could silently return empty result sets or, worse, expose cross-org data, both of which are difficult to detect in unit tests.

Mitigation & Contingency

Mitigation: Write dedicated RLS integration test scenarios with multiple org fixtures asserting both data isolation and correct data visibility. Use Supabase's built-in policy testing utilities and review policies with a second developer.

Contingency: If RLS policies prove too complex to get right quickly, implement application-layer org scoping as a temporary guard while RLS is fixed in a follow-up, with an explicit security review gate before production deployment.

high impact medium prob security

The declaration audit logger must produce tamper-evident records. If the database allows updates or deletes on audit rows, the compliance guarantee is broken. Supabase does not natively prevent row deletion by default.

Mitigation & Contingency

Mitigation: Implement an insert-only RLS policy on the audit table that denies UPDATE and DELETE for all roles including the service role. Add a database trigger that rejects mutation attempts and logs the attempt itself.

Contingency: If immutability cannot be enforced at the database level within the sprint, store audit entries in an append-only Supabase Edge Function log stream as a temporary alternative, with a migration plan to the proper table once constraints are implemented.

medium impact low prob technical

The org-feature-flag-service caches flag values to avoid repeated database reads. If the cache is not invalidated promptly after an admin toggles the flag, coordinators may see stale UI state — either seeing driver features when they should not, or not seeing them when they should.

Mitigation & Contingency

Mitigation: Use a Supabase Realtime subscription to listen for changes on the driver_feature_flag_config table and invalidate the in-memory cache immediately on change. Set a short TTL (60 seconds) as a safety net.

Contingency: If Realtime subscription proves unreliable, expose a manual cache-bust endpoint accessible from the admin toggle action, ensuring the cache is cleared synchronously on every flag change.