Repository navigation
tech-debt: memberships DB table is dead — audit readers, align on Matrix/chain as source of truth #2506
Description
Context
Discovered while building #2470's chat.message/chat.mention notifications: the DB
memberships table (packages/storage-postgres/src/schema/membership.ts) is effectively dead.
Evidence: prod's memberships table has 2 rows total, both created 2025-03-03, none
since — 18+ months of real space/member activity with zero growth. Searched the entire
packages/apps tree for any code that writes to this table (.insert(memberships) or
equivalent) — found none. No webhook, no server action, no sync job in hypha-web populates
it. It looks like an early attempt at a canonical membership table that was superseded by
reading membership/access live from chain (and, for chat, from Matrix room state) without ever
being cleaned up — some code paths kept querying it, unaware the data was stale/absent.
Where this is known to matter today
#2470's new chat notifications (packages/notifications/src/events/chat/resolver.ts)
originally resolved "who's in this space" viamemberships— always returned zero recipients
for any real space. Already fixed in#2470: switched to querying Matrix's own room
membership (matrixGetJoinedRoomMembers,packages/core/src/matrix/server/ matrix-http-client.ts) instead — live, authoritative, and a better architectural fit anyway
since these events are already Matrix-room-sourced.assertCanEditCoherence(packages/core/src/coherence/server/mutations.ts) checks
creator-first, then falls back to amembershipslookup for non-creator editors. If
membershipsis genuinely empty for a space, any member other than a signal's creator has
likely never been able to edit/interact with signals they don't own — a real, currently
silent permissions gap, separate from notifications entirely. Not investigated further here —
flagging for this ticket's own triage.- Likely other call sites exist —
git grep -n membershipsacrosspackages/core/srcto find
every remaining reader before deciding what to do with each one.
Relation to #2450
hypha-web#2450 already raised the same
underlying question for signal_team-scoped call recipients: "does a signal-team concept exist
in the DB, or is it purely inferred — should Matrix room membership be the real source of
truth?" This ticket's finding is the same answer applied more broadly: Matrix room membership
(or on-chain reads, depending on context) is the actual live source of truth across this
codebase; the memberships DB table is not being kept in sync with anything and should either be
properly wired up or retired, not left as a trap for future code to accidentally trust.
Suggested scope (not decided — needs its own triage)
- Audit every remaining reader of the
membershipstable
(git grep -rn "memberships" packages/core/src) and classify each: safe to leave (already
migrated/fixed), needs migrating to Matrix/chain reads, or acceptable to leave with a
documented caveat. - Root-cause and fix
assertCanEditCoherence's non-creator-member edit-permission gap
(item 2 above) — likely its own scoped fix within this ticket. - Decide, together with
#2450: fixmembershipsproperly (start writing to it on real
join/leave events) vs. retire it entirely in favor of Matrix/chain reads everywhere it's
still consulted. - Not in scope: cleaning up the 2 stale prod rows — not worth it, this is about the code
paths, not the data.
Links
#2470— hypha-web#2470 (where this was
discovered and partially fixed for chat)#2450— hypha-web#2450 (same root
question, narrower scope — signal-team call recipients)- Decisions:
hypha-context/progress/members/gerroza/tickets/2470-notification-service-redesign/decisions.mdD21
Addendum (2026-09-24): scheduled items are affected too
-
Scheduled-item invitations and reminders.
resolveScheduledItemRecipientSlugs(packages/core/src/schedule/server/recipients.ts) falls back tofindSpaceMemberSlugsBySpaceId(the deadmembershipstable) whenever the item is not linked to a signal, or the linked signal has no assignees besides the creator. Space-wide meetings and calls therefore resolve to zero recipients, and bothscheduled_item.invitedand the reminders route (api/v1/ops/scheduled-items/dispatch-reminders) silently send nothing.Separate, undiagnosed: in prod testing after feat(notifications): notification service redesign (decision/delivery layer) + Matrix AS receiver #2507, an invite for a meeting linked to a signal (with a valid assignee who does receive
signal.assigned) was also not delivered. It was not investigated. To triage: check prod logs for the createPOST .../scheduled-items(Failed to dispatch scheduled item invitation:), whether rows exist inspace_scheduled_item_invitation_dispatches, and that the item ismeeting/callwith auto-link or a meeting URL and a filledcoherence_id. Not critical; picked up here rather than as its own ticket.
- Migrate the space-wide fallback to a live source (on-chain member read as in
proposal.created, or Matrix room membership) alongside the other readers. - Triage the undiagnosed signal-linked invite non-delivery above.
Activity