high priority medium complexity api pending api specialist Tier 6

Acceptance Criteria

When ExportResult.outputType=FILE, a signed URL is generated for the file path in Supabase Storage with exactly 900-second (15-minute) expiry
Signed URL response body matches ExportResponse interface: {type: 'file', signedUrl: string, expiresAt: string (ISO8601), exportRunId: string}
When ExportResult.outputType=API_PUSH, response body matches: {type: 'api_push', externalRef: string, system: 'xledger' | 'dynamics', exportRunId: string}
Signed URL is generated using the service-role Supabase client so it works for files in private storage buckets
File path used for signed URL generation is taken exclusively from ExportResult — never constructed from user input
HTTP 404 is returned if ExportResult.filePath does not exist in Supabase Storage
HTTP 500 with sanitized error body is returned if signed URL generation fails
HTTP 204 with {type: 'no_data'} body is returned when ExportResult.status=NO_DATA
All response bodies conform to the ExportResponse TypeScript interface defined in types.ts
Content-Type: application/json header is set on all responses

Technical Requirements

frameworks
Deno runtime
Supabase Storage SDK (server-side signed URL generation)
apis
Supabase Storage (createSignedUrl)
Supabase Edge Functions (Deno)
data models
bufdir_export_audit_log
performance requirements
Signed URL generation must complete in under 300ms
Total response time for file branch must be under 500ms after ExportResult is received
No file content should be loaded into memory — only the signed URL is returned
security requirements
Signed URL expiry must be enforced server-side — 900 seconds maximum, not configurable by client
File path for signed URL must come from ExportResult only — never from client request to prevent path traversal
Signed URLs must point to the private exports bucket — not a public bucket
externalRef in API push response must not include credentials or API keys — only the external system's transaction ID
RLS must prevent the signed URL from being usable by a different org's users

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Use `supabaseServiceClient.storage.from('accounting-exports').createSignedUrl(filePath, 900)` for signed URL generation. The 900-second value should be defined as a named constant (SIGNED_URL_EXPIRY_SECONDS) in a config file — do not hardcode inline. The ExportResponse discriminated union should use TypeScript's `type` field as the discriminant for clean narrowing: `if (result.outputType === 'FILE') { ... } else if (result.outputType === 'API_PUSH') { ...

}`. For the expiresAt field, compute it as `new Date(Date.now() + 900_000).toISOString()` at response time. Avoid loading the file contents — the client downloads directly from the signed URL. Consider adding a download_expires_at field to the export run record so the audit trail shows when access expires.

Testing Requirements

Unit tests for the response branch logic using Deno.test with mocked Supabase Storage client. Test cases: (1) FILE branch — mock createSignedUrl success, assert response shape and 200 status; (2) FILE branch — mock createSignedUrl failure, assert 500 response; (3) FILE branch — mock storage 404, assert 404 response; (4) API_PUSH branch — assert confirmation payload shape and 200 status; (5) NO_DATA branch — assert 204 with {type: 'no_data'}; (6) assert signed URL expiry is exactly 900 seconds in all FILE branch calls. Integration tests in task-009 will cover the full end-to-end path including real Storage interactions.

Component
Supabase Edge Function: Generate Export
infrastructure high
Epic Risks (3)
high impact medium prob technical

The Edge Function may exceed Supabase's execution time limit (default 150 seconds, but effectively constrained by the 10-second client SLA) when processing large batches of claims with complex chart-of-accounts mapping, causing the export to fail after partial processing.

Mitigation & Contingency

Mitigation: Implement the export pipeline with early termination on timeout and an in-progress export run status. Add a benchmark test in CI that runs the full pipeline against 500 claims and fails if it exceeds 8 seconds. Optimize the approved claims query with indexes on status, org_id, and date fields.

Contingency: If performance targets cannot be met synchronously, convert the Edge Function to an async job pattern: the function queues the export and returns a job ID immediately; the client polls a status endpoint and downloads the file when ready. This requires a job queue table and a polling UI state.

high impact medium prob security

Supabase Vault access from the Edge Function may require specific service role key configuration that differs between staging and production environments, causing credential retrieval to fail silently and producing export runs that appear successful but have no valid accounting system target.

Mitigation & Contingency

Mitigation: Test Vault read access in the Edge Function in staging before implementing any business logic. Add an explicit credential validation step at Edge Function startup that fails fast with a clear error if Vault is unreachable or the secret is missing.

Contingency: If Vault access fails in production, fall back to environment variable-based credentials temporarily (never returned to client) while the Vault configuration is corrected. Alert on-call via a monitoring rule that fires if credential retrieval fails.

medium impact low prob technical

AccountingExporter Service may become tightly coupled to specific exporter implementations if the factory pattern is not implemented cleanly, making it difficult to add a third exporter in the future without modifying the orchestrator.

Mitigation & Contingency

Mitigation: Define an AccountingExporter abstract class with a strict interface contract before implementing any concrete class. Use a registry pattern (Map<orgType, AccountingExporter>) in the factory rather than conditionals. Code review should verify no concrete class is imported directly in the orchestrator.

Contingency: If tight coupling is discovered after implementation, refactor the factory before the Edge Function epic ships so the interface is stable before any external callers are wired in.