The same person can accept several invitations to the same program, and nothing in the code prevents it.
- Evidence: the
update_or_create key in Involvement.from_accepted_invitation, the lack of any uniqueness or "already a host" check in invite_program_host and AcceptInvitation, and the offerer case above (the role changes and the offer response is lost).
- Suggested direction:
- Reject or deduplicate invitations to someone who is already a host, or accept an invitation only from a matching email.
- Make acceptance idempotent or an explicit edit.
- When it is fixed, also clear stale propagated annotations.
Why nothing stops it
Program.invite_program_host creates an Invitation with no check for an existing invitation or host with the same email or person.
AcceptInvitation only checks that this one invitation hasn't been used. It doesn't check that the accepting user's email matches the invitation's.
Involvement has no unique constraint. from_accepted_invitation uses update_or_create on (universe, person, app, type, program).
What happens
- Same guest accepts two invitations to one program: you get one involvement, not two. The second acceptance overwrites its
response and invitation, so the first response is no longer reachable from program.responses. That property is built from the involvements' current responses.
- The offerer accepts an invitation to their own program: their existing host involvement is overwritten. Its
response changes from the program offer to the invite response, invitation is set, and program_host_role flips from OFFERER to INVITED. The offer response disappears from program.responses, so the program's own offer data stops feeding annotation extraction.
- The same-guest case needs two invitations to the same person, for example the host being invited twice or from two email addresses. The offerer case needs only an invitation sent to the offerer's own address.
Discovered during review of #1080
Review comment: #1080 (comment)
It is valid only because of this behaviour, or when from_involvement re-runs from_accepted_invitation. On a first acceptance there is nothing stale. {**involvement.annotations, **passed_forward_annotations} leaves a key from the first response in place when the second response has no value for it, or has a value that fails validation. The fix would be small: drop the annotation slugs that the survey's fields propagate to, then merge the new ones, so unrelated keys stay. I'd leave it out of this PR and put it in the new issue, since it only matters once repeat acceptance exists.
The same person can accept several invitations to the same program, and nothing in the code prevents it.
update_or_createkey inInvolvement.from_accepted_invitation, the lack of any uniqueness or "already a host" check ininvite_program_hostandAcceptInvitation, and the offerer case above (the role changes and the offer response is lost).Why nothing stops it
Program.invite_program_hostcreates anInvitationwith no check for an existing invitation or host with the same email or person.AcceptInvitationonly checks that this one invitation hasn't been used. It doesn't check that the accepting user's email matches the invitation's.Involvementhas no unique constraint.from_accepted_invitationusesupdate_or_createon(universe, person, app, type, program).What happens
responseandinvitation, so the first response is no longer reachable fromprogram.responses. That property is built from the involvements' current responses.responsechanges from the program offer to the invite response,invitationis set, andprogram_host_roleflips fromOFFERERtoINVITED. The offer response disappears fromprogram.responses, so the program's own offer data stops feeding annotation extraction.Discovered during review of #1080
Review comment: #1080 (comment)
It is valid only because of this behaviour, or when
from_involvementre-runsfrom_accepted_invitation. On a first acceptance there is nothing stale.{**involvement.annotations, **passed_forward_annotations}leaves a key from the first response in place when the second response has no value for it, or has a value that fails validation. The fix would be small: drop the annotation slugs that the survey's fields propagate to, then merge the new ones, so unrelated keys stay. I'd leave it out of this PR and put it in the new issue, since it only matters once repeat acceptance exists.