critical priority high complexity integration pending integration specialist Tier 5

Acceptance Criteria

Accounting credentials (Xledger API key, Dynamics OAuth token) are loaded exclusively from Deno environment variables or Supabase Vault — never from the request body or query params
AccountingExporterService.triggerExport() is called with orgId from JWT claim, dateRange from validated request body, and userId from JWT subject claim
ExportResult is captured and its status field is used to determine the response branch
Every invocation (success, failure, rejected) writes one row to the bufdir_export_audit_log table using the service-role Supabase client
Audit log row contains: triggered_by_user_id, organization_id, report_period_id (derived from dateRange), status, timestamp — no credentials or raw financial data
If AccountingExporterService throws DuplicateExportException, the function returns HTTP 409 with {error: 'Export already exists for this period'}
If AccountingExporterService throws any other exception, the function returns HTTP 500 and the audit log row records status=FAILED with the sanitized error message
Credentials are never present in any log output, response body, or error message returned to the client
Service-role client is only instantiated after the JWT auth guard passes — never at module level

Technical Requirements

frameworks
Deno runtime
Supabase Edge Functions (Deno)
supabase-js (service role client for audit logging)
apis
Supabase PostgreSQL 15 (audit log write)
Xledger REST API (via AccountingExporterService)
Microsoft Dynamics 365 REST API (via AccountingExporterService)
Supabase Edge Functions (Deno)
data models
bufdir_export_audit_log
annual_summary
performance requirements
Total Edge Function execution must stay within the 150-second Supabase Edge Function timeout
Audit log write must be non-blocking — use Promise without awaiting if it risks timeout
Credential vault fetch must complete in under 500ms
security requirements
SUPABASE_SERVICE_ROLE_KEY must be used only for the audit log write — never returned to client
Xledger and Dynamics credentials loaded from Deno.env — never from request, never logged
orgId used for credential lookup must come from JWT claim to prevent cross-org credential access
Audit log must use service-role client to bypass RLS — but only for the audit insert, not for data reads
Error messages returned to client must be sanitized — no stack traces, no credential fragments, no internal table names

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Fetch credentials using `Deno.env.get('XLEDGER_API_KEY')` and `Deno.env.get('DYNAMICS_CLIENT_SECRET')` — these are injected as secrets in the Supabase project settings (not in .env files committed to source control). For multi-tenant credential isolation, prefix env var names with the orgId (e.g., XLEDGER_API_KEY_ORG_ABC) or use Supabase Vault with per-org secret names. The AccountingExporterService in this context runs within the Deno runtime — if it is a Dart service, it must be invoked via an HTTP call to a separate service; if it is TypeScript, import it directly. Audit log writes should use fire-and-forget pattern with a catch that logs to stderr (not to the response) to avoid blocking the response.

Use structured logging (JSON to stderr) for all operational events so they are queryable in Supabase logs.

Testing Requirements

Integration tests using Supabase Functions local test harness (see task-009 for full E2E suite). For this task, write focused integration tests: (1) successful Xledger invocation — assert audit log row created with status=SUCCESS; (2) successful Dynamics invocation — assert audit log row created with status=SUCCESS; (3) DuplicateExportException → 409 response + audit log row with status=REJECTED; (4) generic exception → 500 response + audit log row with status=FAILED; (5) assert credentials are not present in any response payload across all scenarios. Use a test Supabase project with seeded org and user data. Mock the external Xledger and Dynamics endpoints using a local HTTP stub server.

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.