high priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

A generateDeclarationDownloadUrl(declarationId, requestingUserId, orgId, ttlSeconds?) function exists and returns a short-lived signed URL string
Default TTL is 86400 seconds (24 hours); TTL is overridable via the ttlSeconds parameter with a maximum cap of 86400 seconds — values above the cap are clamped, not rejected
The signed URL is scoped to exactly the object path declarations/{orgId}/{declarationId}.enc — it cannot be used to download any other object
Before generating the URL, the adapter verifies that the requesting user's JWT orgId matches the object's orgId path segment — cross-org URL generation returns an AuthorizationError
Before generating the URL, the adapter verifies that the declarations/{orgId}/{declarationId}.enc object exists in storage — if not found, a NotFoundError is returned
Each URL generation event is logged as a new row in declaration_acknowledgement (or a dedicated url_generation_audit table) with fields: declaration_id, requesting_user_id, org_id, generated_at, expires_at
The returned signed URL is a Supabase Storage signed URL (not a public URL) — verified by asserting the URL contains a token query parameter
A signed URL that has expired (TTL elapsed) returns 400 or 403 from Supabase Storage — verified in integration test by generating with TTL=1s and fetching after 2s
The function is exposed as an authenticated Supabase Edge Function endpoint requiring a valid user JWT — unauthenticated calls return 401
Unit tests cover: successful URL generation with default TTL, TTL clamping above max, cross-org rejection, object-not-found error, audit log row insertion

Technical Requirements

frameworks
Supabase Edge Functions (Deno)
Supabase Storage SDK (server-side)
apis
Supabase Storage createSignedUrl API
Supabase Auth (JWT verification)
data models
confidentiality_declaration
declaration_acknowledgement
performance requirements
Signed URL generation must complete within 500ms including the audit log write
Audit log write must not block URL delivery — use Promise.all or fire-and-forget with error logging
security requirements
Signed URL generation executed server-side only — mobile app receives only the URL string, never any storage credentials
JWT org_id claim validated against the storage object path before URL generation — prevents a driver in org A from generating a URL for org B's declarations
Signed URLs must never appear in server-side application logs — log declaration_id and user_id only
TTL cap of 86400 seconds enforced server-side regardless of client-supplied value — prevents indefinite access URLs
Audit log entry (declaration_id, user_id, generated_at, expires_at) written synchronously before returning URL to ensure no URL is issued without an audit trail
Driver's JWT must have role=driver or role=peer_mentor — coordinator and admin roles must use a separate admin download endpoint (out of scope here)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use supabaseAdmin.storage.from('declarations').createSignedUrl(objectPath, ttlSeconds) from the Supabase Storage JS SDK inside the Edge Function. Parse and validate the requesting user's JWT via supabase.auth.getUser() — extract the org_id claim from app_metadata or user_metadata (whichever the project convention is). Before calling createSignedUrl, perform a storage.from('declarations').list(orgPath) or head() to verify object existence — this prevents generating a signed URL for a non-existent or deleted declaration. Write the audit log row to the declaration_acknowledgement table using the service-role admin client (not the user-scoped client) to ensure the write is not blocked by RLS.

Return the signed URL in a JSON response body { url: string, expiresAt: string } — the Flutter client should store expiresAt locally to avoid fetching a URL it knows has expired.

Testing Requirements

Unit tests (Deno test) with mocked Supabase Storage and database clients: assert correct signed URL is returned for valid inputs, assert TTL is clamped to 86400 when 172800 is supplied, assert AuthorizationError when JWT orgId !== path orgId, assert NotFoundError when storage head returns 404, assert audit log insert is called with correct fields. Integration test against Supabase staging: generate URL with TTL=60s and assert the object is downloadable; wait 61 seconds and assert the URL is no longer valid; attempt cross-org URL generation with a JWT for a different org and assert 403.

Component
Declaration Supabase Storage Adapter
data 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.