Repository: bluesky/bluesky (python)

Issue:
BestEffortCallback: Column order not predictable
For years, it has been noticed that the order in which "detector" signals appear in the bec LiveTable & LivePlot seems random.  It is not necessarily the order in which signals are specified nor alphabetical.

For example, in one session:

```py
In [5]: RE(bp.scan([sixcsim.gamma, sixcsim.gamscrew, sixcsim.h, sixcsim.k, sixcsim.l], sixcsim.gamscrew, -1, 11, 5))

Transient Scan ID: 6     Time: 2025-07-16 13:47:16
Persistent Unique Scan ID: '7ca02e6c-ce49-45a8-8ca1-d038cc83153e'
New stream: 'baseline'
New stream: 'primary'
+-----------+------------+------------------+------------+------------+------------+---------------+
|   seq_num |       time | sixcsim_gamscrew |  sixcsim_k |  sixcsim_l |  sixcsim_h | sixcsim_gamma |
+-----------+------------+------------------+------------+------------+------------+---------------+
|         1 | 13:47:16.4 |           -1.000 |     -0.002 |     -0.000 |     -0.000 |        -0.115 |
|         2 | 13:47:16.6 |            2.000 |      0.004 |      0.000 |     -0.000 |         0.229 |
|         3 | 13:47:16.6 |            5.000 |      0.010 |      0.000 |     -0.000 |         0.573 |
|         4 | 13:47:16.6 |            8.000 |      0.016 |      0.000 |     -0.000 |         0.917 |
|         5 | 13:47:16.6 |           11.000 |      0.022 |      0.000 |     -0.000 |         1.260 |
+-----------+------------+------------------+------------+------------+------------+---------------+
generator scan ['7ca02e6c'] (scan num: 6)
Out[5]: ('7ca02e6c-ce49-45a8-8ca1-d038cc83153e',)
```

Restart the session and repeat:

```py
In [2]: RE(bp.scan([sixcsim.gamma, sixcsim.gamscrew, sixcsim.h, sixcsim.k, sixcsim.l], sixcsim.gamscrew, -1, 11, 5))

Transient Scan ID: 8     Time: 2025-07-16 13:48:52
Persistent Unique Scan ID: 'd2e509ad-22e7-4aaa-b6d8-e5c016654de2'
New stream: 'baseline'
New stream: 'primary'
+-----------+------------+------------------+------------+---------------+------------+------------+
|   seq_num |       time | sixcsim_gamscrew |  sixcsim_h | sixcsim_gamma |  sixcsim_l |  sixcsim_k |
+-----------+------------+------------------+------------+---------------+------------+------------+
|         1 | 13:48:52.0 |           -1.000 |     -0.000 |        -0.115 |     -0.000 |     -0.002 |
|         2 | 13:48:52.4 |            2.000 |     -0.000 |         0.229 |      0.000 |      0.004 |
|         3 | 13:48:52.4 |            5.000 |     -0.000 |         0.573 |      0.000 |      0.010 |
|         4 | 13:48:52.4 |            8.000 |     -0.000 |         0.917 |      0.000 |      0.016 |
|         5 | 13:48:52.4 |           11.000 |     -0.000 |         1.260 |      0.000 |      0.022 |
+-----------+------------+------------------+------------+---------------+------------+------------+
generator scan ['d2e509ad'] (scan num: 8)
Out[2]: ('d2e509ad-22e7-4aaa-b6d8-e5c016654de2',)
```

Pull request:
FIX: do not re-order the objects when creating implicit descriptors

<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->

Do not randomize object order when creating implicit descriptors

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

closes #1930 , alternative to #1931 .


## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

Added unit test


<!--
## Screenshots (if appropriate):
-->


Patch:
diff --git a/src/bluesky/bundlers.py b/src/bluesky/bundlers.py
index e970708b..0f72f6b4 100644
--- a/src/bluesky/bundlers.py
+++ b/src/bluesky/bundlers.py
@@ -559,7 +559,8 @@ class RunBundler:
 
         # we do not have the descriptor cached, make it
         if descriptor_doc is None or d_objs is None:
-            for obj in objs_read:
+            # use the dequeue not the set to preserve order
+            for obj in self._objs_read:
                 await self._ensure_cached(obj, collect=isinstance(obj, Collectable))
                 objs_dks[obj] = self._describe_cache[obj]
 


Test patch:
diff --git a/src/bluesky/tests/test_run_engine.py b/src/bluesky/tests/test_run_engine.py
index 3eeaa1cf..8d51622b 100644
--- a/src/bluesky/tests/test_run_engine.py
+++ b/src/bluesky/tests/test_run_engine.py
@@ -22,7 +22,7 @@ from bluesky.plan_stubs import (
     wait,
     wait_for,
 )
-from bluesky.plans import count, grid_scan
+from bluesky.plans import count, grid_scan, scan
 from bluesky.preprocessors import (
     SupplementalData,
     baseline_wrapper,
@@ -2146,3 +2146,28 @@ def test_async_scan_id_source(RE):
     RE.scan_id_source = async_scan_source
     RE([Msg("open_run")])
     assert RE.md["scan_id"] == 42
+
+
+@requires_ophyd
+def test_descriptor_order(RE):
+    from itertools import permutations
+
+    from ophyd import Component, Device, Signal
+
+    class Issue1930(Device):
+        alpha = Component(Signal, value=1, kind="hinted")
+        bravo = Component(Signal, value=2, kind="hinted")
+        charlie = Component(Signal, value=3, kind="hinted")
+
+    i1930 = Issue1930(name="i1930")
+
+    for dets in permutations([i1930.alpha, i1930.bravo, i1930.charlie]):
+        key_order = [d.name for d in dets]
+
+        def check(key_order, name, doc):
+            if name == "event":
+                assert list(doc["data"]) == key_order
+            elif name == "descriptor":
+                assert list(doc["data_keys"]) == key_order
+
+        RE(scan(dets, i1930.charlie, -1, 1, 2), lambda name, doc, key_order=key_order: check(key_order, name, doc))