Skip to content

Rejecting drops a cancelling member's waiting-list entry - #2997

Open
mroderick wants to merge 1 commit into
codebar:masterfrom
mroderick:fix/waiting-list-self-promotion-on-reject
Open

mroderick wants to merge 1 commit into
codebar:masterfrom
mroderick:fix/waiting-list-self-promotion-on-reject

Conversation

@mroderick

@mroderick mroderick commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Rejecting an RSVP while the member is themselves waitlisted can hand the seat back to the person who just cancelled. This PR removes the cancelling member's waiting-list entry during reject, so the promotion goes to the real next entry for the role.

One of five small PRs from the same pre-existing-gap audit against this flow (cross-role promotion pin, promotion email pin, remove-of-consumed-entry guard, token-only reject). Heads are reviewed independently; if a sibling merges first expect a trivial rebase, the branches touch different regions of the same spec file.

Detail

WorkshopInvitationController#reject sets attending: false but leaves the member's WaitingList row in place. Two consequences:

  • The member keeps an entry that can later auto-promote them onto a seat they declined.
  • Worse: with the cancelling member's own entry first on the list, the promotion lookup
    (WaitingList.next_spot(workshop, role)) returns it. The flow then destroys that entry, re-sets the
    freshly cancelled invitation to attending: true, and emails the member they are attending again —
    the self-promotion cascade.

Fix: destroy the cancelling member's entry (WaitingList.find_by(...)&.destroy) before computing the next spot, so a rejection cannot list the member back in.

Review notes
  • Ordering matters: the entry must be destroyed before next_spot is computed; the third new spec (member behind is promoted, canceler not) exercises exactly that.
  • attending: false members can also hold waitlist entries (a member who joined the list then cancelled), so the lookup uses find_by without an attending filter, same as the existing delete flow.
  • Deliberately out of scope: the closed-waitlist view guard, the destroy gate matrix, admin removal between freeze and start, promote_next update-failure path, concurrency, and the promotion/freeze boundary — all owned by feat/post-close-rsvp-waitlist (which renames next_spot to promote_next; the rule above carries over).

Sibling PRs:

WorkshopInvitationController#reject sets `attending: false` but leaves the
member's WaitingList row in place. With the cancelling member's own entry
first on the list, `WaitingList.next_spot` returns it for the seat they just
freed: the flow destroys that entry, re-sets the freshly cancelled
invitation to attending and emails the member they are attending again.

Destroy the cancelling member's entry before looking up the next spot so
the promotion goes to the real next entry for the role.
@mroderick
mroderick force-pushed the fix/waiting-list-self-promotion-on-reject branch from ac8fd0f to f49cbcc Compare October 9, 2026 06:57

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant