-
-
Notifications
You must be signed in to change notification settings - Fork 10
feat(organizations): make last-owner guard atomic (requires MongoDB transactions) #3404
Description
Problem
The validateLastOwnerProtection function in organizations.membership.service.js uses a separate count() call before mutating. Two concurrent owner demotions/removals/leaves can both observe ownerCount === 2 and proceed, leaving the organization with zero active owners.
Root cause
The count check and subsequent mutation are not atomic. Without a MongoDB transaction, there is a race window between the count and the update.
Constraint
The codebase currently avoids MongoDB transactions for standalone MongoDB compatibility (no replica set required — see migration comment). Transactions require a replica set.
Expected behaviour
The last-owner invariant should be enforced atomically. Options:
- Enable replica sets and use MongoDB transactions in
validateLastOwnerProtectionto wrap count+mutate - Use a conditional
findOneAndUpdatethat embeds the count check in the update condition (requires MongoDB aggregation pipeline updates, available in 4.2+) - Add a database-level unique constraint or trigger
Context
Raised by CodeRabbit review of PR #3403 (refactor/module-decoupling). The current implementation is functionally correct under normal (non-concurrent) load, but is racy under concurrent owner operations.