DescriptorScriptPubKeyMan::TopUpWithDB() expands the descriptor from m_max_cached_index + 1, which is 0 for a freshly imported descriptor regardless of its range_start. Importing a descriptor with "range": [N, M] therefore derives and watches every index in [0, M), so the cost of an import is O(M) rather than O(M - N) and the wallet treats scriptPubKeys below N as its own. Load() only expands [range_start, range_end), so those scriptPubKeys stop being IsMine the next time the wallet is opened, and a transaction picked up for one of them during the import rescan is no longer credited to the wallet.
Since ParseDescriptorRange() only bounds the size of the range and not its start, "range": [2146483648, 2147483647] is accepted and expands 2^31 indexes into m_map_script_pub_keys before the RPC returns.
Start the top up loop at range_start and record the index that was actually cached instead of counting from -1, so that Load() and TopUpWithDB() agree on m_max_cached_index for descriptors with a non-zero range_start. Before this change Load() left m_max_cached_index at range_end - range_start - 1 for such descriptors, which made the first top up after opening the wallet re-expand the stored range.
Importing wpkh(xpub/0/*) with "range": [600000, 600009] into a blank watch-only wallet goes from 19.5s to 0.09s on this machine.