critical priority high complexity backend pending backend specialist Tier 4

Acceptance Criteria

sendDeclaration(templateId, driverContext, driverId) executes the full pipeline: render → encrypt → upload → persist → notify in that order
On success, a Declaration record is persisted in Supabase with status = SENT, encrypted_storage_path, template_id, template_version, driver_id, org_id, sent_at (UTC), and content_hash of the plaintext document
On success, the notification service is invoked and the driver receives a push notification — method returns a DeclarationSendResult containing the declaration ID
If render throws (MissingTemplateVariableException, TemplateNotFoundException), no downstream steps execute and the exception propagates — no database state written
If encryption throws, no upload occurs and no database record is written
If upload throws, no database record is written — any partially uploaded Supabase Storage object is deleted via compensating call
If database persist throws after successful upload, the uploaded Supabase Storage object is deleted via compensating call — caller receives DeclarationPersistException
If notification service throws after successful persist, the declaration record remains in SENT status and the error is logged — notification failure does NOT roll back the send (notification is best-effort)
The content_hash stored in the declaration record is a SHA-256 hash of the plaintext (pre-encryption) UTF-8 bytes — used as the e-signature document hash in task-010
The orchestration method is idempotent for the same (templateId, driverId, orgId, assignmentId) within a 60-second window — a duplicate call returns the existing DeclarationSendResult without re-executing the pipeline

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
flutter_test
apis
Supabase PostgREST REST API (declarations table insert)
Supabase Storage API (upload encrypted file)
DeclarationEncryptionService (encrypt plaintext to bytes)
DeclarationStorageAdapter (upload bytes, delete by path)
NotificationService (sendPushToDriver)
data models
Declaration (id, driver_id, org_id, template_id, template_version, status, encrypted_storage_path, content_hash, sent_at, assignment_id)
DeclarationStatus (enum: SENT, ACKNOWLEDGED, EXPIRED)
DeclarationSendResult (declarationId, sentAt, contentHash)
performance requirements
End-to-end pipeline (render + encrypt + upload + persist + notify) must complete within 5 seconds under normal network conditions
Supabase Storage upload uses multipart only if document exceeds 6 MB — declarations are expected to be < 100 KB so single-part upload is the norm
security requirements
Plaintext document must be zeroed from memory immediately after encryption — use a Uint8List and call fillRange(0, length, 0) before allowing GC
encrypted_storage_path must be a non-guessable UUID-based path, not derived from driver name or assignment date
Supabase Storage bucket for declarations must be private (no public URL) — access is via signed URLs only, generated server-side
content_hash (SHA-256) must be computed before encryption so it reflects the document the driver will read

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Model the pipeline as a series of Result steps using a simple Either type or Dart's pattern matching on sealed classes — avoid deeply nested try/catch. Define a DeclarationSendOrchestrator class that takes all five dependencies via constructor injection (Riverpod). The compensating delete pattern: keep a nullable String? uploadedStoragePath local variable; set it after upload succeeds; in the catch block, if uploadedStoragePath != null, call storageAdapter.delete(uploadedStoragePath) in a try/catch (log but don't rethrow compensating failures).

For idempotency: before the pipeline, query the declarations table for an existing record with (driver_id, assignment_id, org_id) created within the last 60 seconds — if found, return its DeclarationSendResult immediately. Use dart:crypto SHA256 for content_hash: sha256.convert(utf8.encode(plaintext)).toString(). The notification failure tolerance is intentional and should be documented in code comments — it reflects the business decision that a sent declaration is authoritative even if the push fails.

Testing Requirements

Unit tests with all five dependencies mocked (renderer, encryption service, storage adapter, declaration repository, notification service): happy path, render failure (no downstream calls), encryption failure, upload failure (compensating delete called), persist failure after upload (compensating delete called), notification failure (declaration stays SENT, no rollback). Verify compensating delete is called exactly once on upload-failure and persist-failure paths. Integration test: run the full pipeline against Supabase test project, verify declaration row in database, verify encrypted file exists in Storage, verify content_hash is correct SHA-256 of plaintext. Verify idempotency: call sendDeclaration twice with same params within 60 s, assert only one declaration row created.

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.