The refill's allowance for sidecar-carried assignments was computed once,
from the rows fetched BEFORE it ran, so every moved conversation the refill
itself reached spent an unassigned slot again -- the failure the allowance
was added to remove:
* 30 conversations, 3 moved from the WebUI at the bottom edge of the 20-row
window (cli-08..cli-10): 19 unassigned rows in the payload, and cli-07 --
genuinely unassigned, present in state.db -- never fetched at all;
* 40 conversations, 11 moved straddling the boundary (cli-15..cli-25): 15
unassigned rows, with cli-09..cli-13 never fetched.
Both deliver 20 now. The refill re-classifies its OWN result and widens
again while the window is short. It widens proportionally (by the moved
share observed so far), not one row at a time, so it converges for moves
that cluster as well as for moves spread through the history.
Bounds per sidebar build: at most UNASSIGNED_CLI_REFILL_MAX_QUERIES (4)
unassigned queries, none reading deeper than
UNASSIGNED_CLI_REFILL_SCAN_CEILING (200) conversations, stopping as soon as
the window is full or the filter demonstrably has nothing deeper to give.
The last of the four reads the whole ceiling band, so the window fills
whenever at least 20 of the newest 200 state.db-unassigned conversations
are still unassigned once their sidecars are read. Past that (>180 of the
newest 200 moved from the WebUI) the sidebar is honestly short instead of
paying unbounded I/O -- that boundary is stated in the code and pinned by
a test.
No I/O per row is added: the re-count reuses the scan's memoized sidecar
and project-catalog caches (#4842).