Conversations could get reassigned repeatedly in quick succession —
bouncing between agents with several "Assigned to X" activity messages
in a row — whenever an assignment write raced against another assignment
write on the same conversation (an automation rule vs. Assignment V2's
auto-assign, two manual assignment clicks, two concurrent status
transitions triggering legacy round-robin, etc). This was reproducible
with or without Assignment V2 enabled; it wasn't a V2-specific issue,
just more visible under message bursts. Assignment V2's own job-vs-job
race was already fixed separately (#14495) — this PR covers the other
writers that don't check the conversation's current state before
overwriting it.
## How to reproduce
Configure an automation rule that assigns an agent/team on
`message_created`, then send a burst of messages on one conversation
from a channel/inbox with concurrent message delivery (or race it
against another assignment source — a manual assignment click, or a
status change that triggers legacy auto-assignment). The conversation's
assignee flips between agents multiple times, each producing its own
activity message, even though later writes were often redundant (setting
the assignee to a value it already had, from the writer's stale point of
view).
## What changed
Three assignment write paths now take a row lock (`with_lock`) before
writing, so a concurrent writer re-reads the conversation's true current
state before deciding whether a write is actually needed:
- `ActionService#assign_agent`/`#assign_team` (and the corresponding
unassign methods) — used by automation rules, macros, and delayed
automations.
- `Conversations::AssignmentService#assign_agent`/`#assign_agent_bot` —
the dashboard's assignee dropdown (human agent or AgentBot).
- `AutoAssignment::AgentAssignmentService#perform` — the legacy (non-V2)
round-robin auto-assignment path.
A write that would just reapply an already-current value becomes a
genuine no-op instead of producing a redundant `UPDATE` and duplicate
activity message. Legitimate reassignment (a real change in target, or
reassignment away from an agent who lost inbox access) is unaffected.