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
| Function | Called from | What actually happened |
|---|---|---|
get_events_with_friends | event_notifier.loadEvents — the home feed | Selected 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_events | event_api_service.getNearbyEvents | Never 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_reaction | messaging_api_service.reactToMessage | Never 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
catchthat 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:
- Privacy —
p_user_idwas whatever the caller passed, so anyone could supply another person’s uuid and read which events their friends were attending. Now forced toauth.uid(). - Beta gate —
SECURITY DEFINERbypasses RLS, so the function was a way straight around the closed-beta restriction. An explicitis_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.
Related
audit-unused-infrastructure · feat-taxonomy-covers-geo-kpi · database-schema