high priority medium complexity infrastructure pending infrastructure specialist Tier 3

Acceptance Criteria

Edge function is triggered correctly on activity-registered database webhook or HTTP POST from the Flutter app
Function validates the JWT from the Authorization header and rejects requests with invalid or expired tokens (returns 401)
Function rejects requests where the mentorId in the payload does not match the JWT subject (returns 403)
Function invokes badge evaluation logic and calls badge-award-service for each newly earned badge ID
Function returns a JSON response body: { awarded: string[], errors: string[] } where awarded lists badge IDs successfully awarded
If badge-award-service fails for one badge, the function continues processing remaining badges and records the failure in the errors array
If evaluation returns no new badges, function returns HTTP 200 with { awarded: [], errors: [] } — not an error
Org-scoped RLS is enforced: function can only read/write badge data belonging to the organisation of the authenticated user
Function emits a Supabase Realtime event (or inserts into a notifications table) for each awarded badge so the Flutter client can react
Integration test covers: valid trigger → evaluation → award → 200 response with correct awarded IDs
Integration test covers: invalid JWT → 401 response
Integration test covers: evaluation returns zero badges → 200 with empty awarded array
Edge function cold start time is under 2 seconds

Technical Requirements

frameworks
Supabase Edge Functions (Deno/TypeScript)
apis
Supabase Auth — JWT validation via supabaseAdmin.auth.getUser(token)
badge-evaluation-service (ported or called as internal module)
badge-award-service
Supabase Realtime — insert into badge_events table to trigger client subscription
data models
BadgeDefinition
EarnedBadge
PeerMentorStats
BadgeAwardEvent
performance requirements
Edge function total execution time under 3 seconds for up to 100 badge definitions
Badge award loop processes awards in parallel using Promise.all() where independent
security requirements
Validate Authorization: Bearer <JWT> header on every request — reject missing or malformed tokens with 401
Verify mentorId in request body matches JWT sub claim — reject mismatch with 403
Use Supabase service role key only for admin operations (award writes); use user-scoped client for reads to enforce RLS
Never expose internal error stack traces in HTTP response bodies — log to Supabase function logs, return generic message to client
Rate-limit invocations per mentorId to prevent badge-farming via rapid activity registration (implement via Supabase database rate-limit table or external check)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Structure the Edge Function as a standard Deno HTTP handler in functions/badge-criteria/index.ts. Use the @supabase/supabase-js client initialised with the request's JWT for RLS-enforced reads, and a separate admin client (SERVICE_ROLE_KEY from Deno.env) only for writes that require bypassing RLS (e.g., inserting earned badges). Port the badge evaluation criteria logic from the Dart BadgeEvaluationService into a TypeScript module at functions/_shared/badgeEvaluation.ts — shared across Edge Functions to avoid drift. Use Promise.all() for parallel badge award calls but wrap each in a try/catch to collect partial failures.

Emit Realtime events by inserting a row into a badge_events table with columns: mentor_id, badge_id, org_id, awarded_at — the Flutter client subscribes to this table filtered by mentor_id. Set a function timeout of 10 seconds in supabase/config.toml. Document all environment variables required (SERVICE_ROLE_KEY, SUPABASE_URL) in a .env.example file adjacent to the function.

Testing Requirements

Write integration tests using Supabase local development (supabase start) and the Deno test runner: (1) POST with valid JWT and a mentorId that qualifies for two badges returns 200 with both badge IDs in awarded array, (2) POST with expired JWT returns 401, (3) POST where mentorId mismatches JWT sub returns 403, (4) evaluation returning zero new badges returns 200 with empty awarded array, (5) badge-award-service failure for one badge is captured in errors array without aborting remaining awards. Seed test database with known badge definitions, mentor stats, and earned badges for deterministic evaluation. Verify Realtime events are emitted by subscribing to the badge_events table in the test and asserting the event payload matches awarded badge IDs. All tests must run against local Supabase instance — no calls to production.

Component
Badge BLoC
infrastructure medium
Epic Risks (2)
medium impact medium prob integration

The badge-earned-celebration overlay must appear within 2 seconds of the triggering activity being saved, but badge evaluation runs server-side in an edge function triggered by a database webhook. Network latency, edge function cold start, and Supabase Realtime delivery delays could cause the overlay to appear late or not at all, breaking the motivational loop.

Mitigation & Contingency

Mitigation: Implement an optimistic UI path: after activity save, badge-bloc immediately checks whether any badge thresholds are crossed client-side using cached stats and badge definitions, showing the overlay speculatively before server confirmation. The server result then reconciles. Subscribe to Supabase Realtime on the earned_badges table for authoritative confirmation.

Contingency: If Realtime delivery is unreliable in production, add a polling fallback: badge-bloc polls for new earned badges 3 seconds after an activity save and shows the overlay if a new record is detected, accepting up to 5-second latency as a fallback SLA.

high impact low prob technical

The celebration overlay uses animation for positive reinforcement, but motion sensitivity (prefers-reduced-motion) and screen reader users require a non-animated or text-only alternative. Failing to handle this risks excluding Blindeforbundet users or triggering vestibular discomfort for motion-sensitive volunteers.

Mitigation & Contingency

Mitigation: Check MediaQuery.disableAnimations in badge-earned-celebration-overlay and skip animation entirely when true, showing a static card instead. Add an ExcludeSemantics wrapper around the decorative animation widget and a separate Semantics node with a live region announcement of the badge name and congratulatory message.

Contingency: If accessibility issues are identified in TestFlight testing with Blindeforbundet's test group, fast-track a patch that defaults to the static card path and gates the animation behind a user preference setting in notification preferences.