critical priority low complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

Directory structure created at supabase/functions/threshold-validation/ with index.ts as the entry point
index.ts exports a Deno.serve handler that accepts POST requests with Content-Type: application/json
Request body type definition includes: claim_id (string UUID), client_computed_result (object with approval_path enum and total_amount number)
Response type definition includes: approved_path (enum: auto_approved | manual_required), server_total (number), threshold_applied (number), organisation_id (string)
Supabase service role client initialised using Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') — never hardcoded
Function reads the caller's JWT from Authorization header and validates it before processing
Calling user's organisation_id is extracted from the validated JWT claims, not from the request body
Function returns HTTP 400 for malformed request body with descriptive error message
Function returns HTTP 401 for missing or invalid JWT
supabase/functions/threshold-validation/deno.json (or import_map.json) configured with correct Supabase client import version
Function deployable via `supabase functions deploy threshold-validation` without errors

Technical Requirements

frameworks
Deno
TypeScript
Supabase Edge Functions runtime
apis
Supabase Edge Functions (Deno)
Supabase PostgreSQL 15 via service role client
performance requirements
Cold start must complete within 1 second
Function scaffold (no business logic) must respond in under 100ms
security requirements
SUPABASE_SERVICE_ROLE_KEY loaded exclusively from Deno.env — never from request body or query params
JWT validated using Supabase's built-in auth.getUser() before any business logic executes
organisation_id extracted from verified JWT claims — client cannot inject arbitrary org scope
CORS headers restricted to the app's origin — no wildcard Access-Control-Allow-Origin in production
Function input strictly typed — unknown fields rejected with 400

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Follow the standard Supabase Edge Function boilerplate: `import { serve } from 'https://deno.land/std/http/server.ts'` and `import { createClient } from 'https://esm.sh/@supabase/supabase-js'`. Use `createClient(url, serviceRoleKey, { auth: { persistSession: false } })` for the RLS-bypassing admin client. Additionally create a per-request user-scoped client from the incoming JWT for operations that should respect RLS.

Structure the handler to: (1) parse and validate JWT, (2) extract org context, (3) parse and validate request body, (4) call business logic (stub for now), (5) return typed response. Keep all environment variable access at the top of the file in a single config object for easy auditing. Add a README.md inside the function directory documenting the expected request/response contract.

Testing Requirements

Manual smoke test: deploy to Supabase staging environment and POST a valid request with a real JWT — verify 200 response with placeholder data. POST without Authorization header — verify 401. POST with malformed JSON body — verify 400 with descriptive error. Add a Deno unit test file (threshold_validation_test.ts) that tests the request parsing and JWT extraction logic with mocked Supabase client.

This test file runs in CI via `deno test`.

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.