Skip to content

feat(organizations): make last-owner guard atomic (requires MongoDB transactions) #3404

@PierreBrisorgueil

Description

@PierreBrisorgueil

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 validateLastOwnerProtection to wrap count+mutate
  • Use a conditional findOneAndUpdate that 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FeatA new feature

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions