Skip to content

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

Merged
olleolleolle merged 2 commits into
codebar:masterfrom
mroderick:fix/waiting-list-self-promotion-on-reject
Oct 11, 2026
Merged

olleolleolle merged 2 commits 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
MemberActivityRecorder.record(actor: @invitation.member, key: 'workshop_invitation.rejected',
trackable: @invitation)

WaitingList.find_by(invitation_id: @invitation.id)&.destroy

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: if there is an association invitation on WaitingList, it can be used in the find_by args, like invitation: @invitation. ActiveRecord will iron that out into SQL.

@olleolleolle
olleolleolle merged commit 82203f1 into codebar:master Oct 11, 2026
10 checks passed
mroderick added a commit to mroderick/codebar-planner that referenced this pull request Oct 12, 2026
promote_next dropped the self-exclusion guard that PR codebar#2997 built into
the reject flow with find_by(...)&.destroy before the next-spot lookup.
With the cancelling member's own entry first (often the only one), the
promotion confirmed their just-cancelled invitation and emailed them an
attendance notice instead of reaching the real next member.

promote_next now takes excluding:, and both callers pass the invitation
being cancelled. Update the controller examples: the rejecter's entry
stays on the list until they leave it, rather than being consumed by
their own cancellation.
@mroderick
mroderick deleted the fix/waiting-list-self-promotion-on-reject branch October 12, 2026 16:46
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.

2 participants