Problem
tblOrgInvitations.UQ_org_email_pending (orgHandle, email, status) includes status, so only one row per lifecycle state is allowed. A second cancel/expire for the same org+email collides with the unique key, so re-invite cycles fail.
Affected
web/_sql/schema/014_org_invitations.sql
web/_functions/org.php (invite/cancel/expire flow)
Fix
Enforce "one PENDING per org+email" only — e.g. a generated column pendingKey = IF(status='pending', CONCAT(orgHandle,'|',email), NULL) with a UNIQUE key (InnoDB exempts NULLs), and drop status from the guarantee.
Acceptance criteria
From the 2026-06-04 schema review — see docs/SCHEMA_REVIEW_2026-06-04.md.
Problem
tblOrgInvitations.UQ_org_email_pending (orgHandle, email, status)includesstatus, so only one row per lifecycle state is allowed. A second cancel/expire for the same org+email collides with the unique key, so re-invite cycles fail.Affected
web/_sql/schema/014_org_invitations.sqlweb/_functions/org.php(invite/cancel/expire flow)Fix
Enforce "one PENDING per org+email" only — e.g. a generated column
pendingKey = IF(status='pending', CONCAT(orgHandle,'|',email), NULL)with a UNIQUE key (InnoDB exempts NULLs), and dropstatusfrom the guarantee.Acceptance criteria
From the 2026-06-04 schema review — see
docs/SCHEMA_REVIEW_2026-06-04.md.