critical priority medium complexity infrastructure pending infrastructure specialist Tier 2

Acceptance Criteria

When server-computed approval path matches client-submitted value, the function returns HTTP 200 with a JSON body containing { approved_path: string, authoritative: true }
When server-computed approval path differs from client-submitted value, the function returns HTTP 422 with a structured error body containing { error: 'CLIENT_DATA_TAMPERED_OR_STALE', server_path: string, client_path: string, claim_id: string }
Every mismatch is recorded in the audit log table with columns: claim_id, user_id, client_path, server_path, ip_address, timestamp (UTC), event_type='APPROVAL_PATH_MISMATCH'
The HTTP 200 response body includes only the authoritative approval path — never the raw client-submitted value
Malformed request body (missing required fields) returns HTTP 400 with field-level validation errors
Requests without a valid Supabase JWT return HTTP 401 before any comparison logic runs
The audit log write must not block the 422 rejection response; failures to write the audit log are caught and logged to stderr without changing the HTTP response code
Response time for the comparison + audit write path is under 300 ms at p95 under normal load
All response bodies conform to a typed interface and are validated before sending

Technical Requirements

frameworks
Supabase Edge Functions (Deno runtime)
Supabase JS client (supabase-js v2) for audit log insert
apis
Supabase REST API (audit log table insert)
Supabase Auth (JWT verification via Authorization header)
data models
ApprovalPath (enum or string literal union: 'auto_approved' | 'manual_review')
ThresholdValidationRequest (claim_id, amount, client_approval_path)
ThresholdValidationResponse (approved_path, authoritative)
ApprovalMismatchAuditEvent (claim_id, user_id, client_path, server_path, ip_address, timestamp, event_type)
performance requirements
Comparison logic completes in under 10 ms
Audit log insert is non-blocking (fire-and-forget with error capture)
Total Edge Function cold-start + execution under 500 ms
security requirements
JWT must be verified before any business logic executes; use Supabase's built-in auth.getUser()
Never return the server-computed path in a 422 error body to avoid leaking approval thresholds to a tampered client — return only an opaque mismatch indicator
Audit log rows must store the requesting user_id (from JWT sub claim) for forensic traceability
IP address capture from request headers (x-forwarded-for with fallback to remoteAddr) for security monitoring
Row-level security on the audit log table must prevent users from reading or modifying their own audit rows

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Structure the Edge Function into three pure, testable layers: (a) `validateRequest(req)` — parse and validate the incoming JSON body, return typed DTO or throw 400; (b) `compareApprovalPaths(serverPath, clientPath)` — pure function returning a discriminated union `{ match: true } | { match: false, auditPayload: AuditEvent }`; (c) `buildResponse(compareResult, serverPath)` — construct the final Response object. Keep the audit log write in the handler layer, wrapped in try/catch so it never propagates. Use TypeScript strict mode. Define all interfaces in a shared `types.ts` within the Edge Function directory.

Avoid importing from the Flutter client — types must be duplicated or shared via a separate package if needed. For the audit log, prefer a direct `supabase.from('approval_audit_log').insert(payload)` over RPC to keep the call simple and auditable. Do NOT return the server-computed threshold value or business rule details in any error response.

Testing Requirements

Integration tests (covered by task-012) are primary. For this task, add unit tests using Deno's built-in test runner: (1) pure comparison function — assert HTTP 200 for matching paths, assert HTTP 422 for mismatched paths; (2) audit payload builder — assert all required fields are present and correctly typed; (3) error serializer — assert 422 body structure matches the typed interface. Mock the Supabase client in unit tests. Aim for 100% branch coverage on the comparison and serialization logic.

No golden image tests needed.

Component
Threshold Validation Supabase Edge Function
infrastructure medium
Epic Risks (3)
high impact high prob technical

The ThresholdEvaluationService is described as shared Dart logic used both client-side and in the Edge Function. Supabase Edge Functions run Deno/TypeScript, not Dart, meaning the threshold logic must be maintained in two languages and can diverge, causing the server to reject legitimate client submissions.

Mitigation & Contingency

Mitigation: Implement the threshold logic as a single TypeScript module in the Edge Function and call it via a thin Dart HTTP client wrapper for client-side preview feedback only. The server is always authoritative; the client version is purely for UX (showing the user whether their claim will auto-approve before they submit).

Contingency: If dual-language maintenance is unavoidable, create a shared golden test file (JSON fixtures with inputs and expected outputs) that is run against both implementations in CI to detect divergence immediately.

medium impact medium prob technical

A peer mentor could double-tap the submit button or a network retry could trigger a duplicate submission, causing the ApprovalWorkflowService to attempt two concurrent state transitions from draft→submitted for the same claim, potentially resulting in two audit events or conflicting statuses.

Mitigation & Contingency

Mitigation: Implement idempotency in the ApprovalWorkflowService using a database-level unique constraint on (claim_id, from_status, to_status) per transition, combined with a UI-level submission lock (disable button after first tap until response returns).

Contingency: Add a deduplication check at the start of every state transition method that returns the existing state if an identical transition is already in progress or completed within the last 10 seconds.

high impact medium prob scope

Claims with multiple expense lines (e.g., mileage + parking) must have their combined total evaluated against the threshold. If individual lines are added asynchronously or the evaluation runs before all lines are persisted, the auto-approval decision may be computed on an incomplete set of expense lines.

Mitigation & Contingency

Mitigation: The Edge Function always fetches all expense lines from the database (not from the client payload) before computing the threshold decision. Define a clear claim submission contract that requires all expense lines to be persisted before the submit action is called.

Contingency: Add a validation step in ApprovalWorkflowService that counts expected vs. persisted expense lines before allowing the transition, returning a validation error if lines are missing.