Summary

Three database functions were being called by the app and did not exist, or existed but raised on every call. None produced a visible error, because each call site caught the failure and fell back to something that returned data. A fallback that always succeeds hides the fact that the real path never runs.

The three

FunctionCalled fromWhat actually happened
get_events_with_friendsevent_notifier.loadEvents — the home feedSelected e.tags, e.price, e.currency, dropped in the 2026-04/05 cleanup. Raised 42703 on every call. Fell back to an unfiltered getEvents(), which is why the home screen recommended events that had finished months earlier.
get_nearby_eventsevent_api_service.getNearbyEventsNever written. Fell back to fetching 200 rows and filtering client-side with a bounding box: 200 rows for 20 results, corners up to 41% beyond the radius, silent truncation past the cap, ordered by date rather than distance.
toggle_message_reactionmessaging_api_service.reactToMessageNever written. Tapping an emoji did nothing; the catch logged a warning and returned false.

Why none of them were noticed

Every one had this shape:

try {
  return await client.rpc('...');      // never succeeded
} catch (_) {                          // note: no log
  return somethingElse();              // returned plausible data
}

The fallback returned rows, the screen rendered, nothing looked broken. The get_events_with_friends failure additionally meant “friends going” social proof has never once appeared — and because it was always empty, the event card’s facepile drew placeholder circles instead, with clamp(1, 3) showing a face even for events with zero attendees. One silent failure produced a fabricated social signal two layers away.

Rule worth keeping

A catch that falls back must log. Not for the user — for whoever reads the logs six months later wondering why a feature “works” but the data looks odd.

Fixed alongside

Repairing get_events_with_friends surfaced two real security holes:

  • Privacyp_user_id was whatever the caller passed, so anyone could supply another person’s uuid and read which events their friends were attending. Now forced to auth.uid().
  • Beta gateSECURITY DEFINER bypasses RLS, so the function was a way straight around the closed-beta restriction. An explicit is_approved() check restores it.

toggle_message_reaction has the same shape of guard: chat membership is checked by hand, because SECURITY DEFINER would otherwise let anyone write into any conversation.

audit-unused-infrastructure · feat-taxonomy-covers-geo-kpi · database-schema