diff --git a/veilid-cli/src/command_processor.rs b/veilid-cli/src/command_processor.rs
index cab0aaa2..5bd05bd7 100644
--- a/veilid-cli/src/command_processor.rs
+++ b/veilid-cli/src/command_processor.rs
@@ -187,8 +187,14 @@ reply               - reply to an AppCall not handled directly by the server
         let ui = self.ui_sender();
         spawn_detached_local(async move {
             match capi.server_debug(rest.unwrap_or_default()).await {
-                Ok(output) => ui.display_string_dialog("Debug Output", output, callback),
-                Err(e) => ui.display_string_dialog("Debug Error", e.to_string(), callback),
+                Ok(output) => {
+                    ui.add_node_event(Level::Debug, output);
+                    ui.send_callback(callback);
+                }
+                Err(e) => {
+                    ui.add_node_event(Level::Error, e.to_string());
+                    ui.send_callback(callback);
+                }
             }
         });
         Ok(())
diff --git a/veilid-cli/src/ui.rs b/veilid-cli/src/ui.rs
index 5d7e1e4d..177407ee 100644
--- a/veilid-cli/src/ui.rs
+++ b/veilid-cli/src/ui.rs
@@ -14,8 +14,8 @@ use cursive::CursiveRunnable;
 use cursive_flexi_logger_view::{CursiveLogWriter, FlexiLoggerView};
 // use cursive_multiplex::*;
 use std::collections::{HashMap, VecDeque};
+use std::io::Write;
 use thiserror::Error;
-
 //////////////////////////////////////////////////////////////
 ///
 struct Dirty<T> {
@@ -454,20 +454,52 @@ impl UI {
         Self::command_processor(s).start_connection();
     }
 
+    fn copy_to_clipboard<S: AsRef<str>>(s: &mut Cursive, text: S) {
+        if let Ok(mut clipboard) = arboard::Clipboard::new() {
+            // X11/Wayland/other system copy
+            if clipboard.set_text(text.as_ref()).is_ok() {
+                let color = *Self::inner_mut(s).log_colors.get(&Level::Info).unwrap();
+                cursive_flexi_logger_view::push_to_log(StyledString::styled(
+                    format!(">> Copied: {}", text.as_ref()),
+                    color,
+                ));
+            } else {
+                let color = *Self::inner_mut(s).log_colors.get(&Level::Warn).unwrap();
+                cursive_flexi_logger_view::push_to_log(StyledString::styled(
+                    format!(">> Could not copy to clipboard"),
+                    color,
+                ));
+            }
+        } else {
+            // OSC52 clipboard copy for terminals
+            if std::io::stdout()
+                .write_all(
+                    format!(
+                        "\x1B]52;c;{}\x07",
+                        data_encoding::BASE64.encode(text.as_ref().as_bytes()),
+                    )
+                    .as_bytes(),
+                )
+                .is_ok()
+            {
+                if std::io::stdout().flush().is_ok() {
+                    let color = *Self::inner_mut(s).log_colors.get(&Level::Info).unwrap();
+                    cursive_flexi_logger_view::push_to_log(StyledString::styled(
+                        format!(">> Copied: {}", text.as_ref()),
+                        color,
+                    ));
+                }
+            }
+        }
+    }
+
     fn on_submit_peers_table_view(s: &mut Cursive, _row: usize, index: usize) {
         let peers_table_view = UI::peers(s);
         let node_id = peers_table_view
             .borrow_item(index)
             .map(|j| j["node_ids"][0].to_string());
         if let Some(node_id) = node_id {
-            let mut clipboard = arboard::Clipboard::new().unwrap();
-            clipboard.set_text(node_id.clone()).unwrap();
-
-            let color = *Self::inner_mut(s).log_colors.get(&Level::Info).unwrap();
-            cursive_flexi_logger_view::push_to_log(StyledString::styled(
-                format!(">> NodeId Copied: {}", node_id),
-                color,
-            ));
+            Self::copy_to_clipboard(s, node_id);
         }
     }
 
@@ -967,10 +999,18 @@ impl UISender {
     pub fn set_config(&mut self, config: &json::JsonValue) {
         let mut inner = self.inner.lock();
 
-        inner
-            .ui_state
-            .node_id
-            .set(config["network"]["routing_table"]["node_id"].to_string());
+        let node_ids = &config["network"]["routing_table"]["node_id"];
+
+        let mut node_id_str = String::new();
+        for l in 0..node_ids.len() {
+            let nid = &node_ids[l];
+            if !node_id_str.is_empty() {
+                node_id_str.push_str(" ");
+            }
+            node_id_str.push_str(nid.to_string().as_ref());
+        }
+
+        inner.ui_state.node_id.set(node_id_str);
     }
     pub fn set_connection_state(&mut self, state: ConnectionState) {
         {
diff --git a/veilid-core/src/crypto/byte_array_types.rs b/veilid-core/src/crypto/byte_array_types.rs
index 8a1f7a01..5590e467 100644
--- a/veilid-core/src/crypto/byte_array_types.rs
+++ b/veilid-core/src/crypto/byte_array_types.rs
@@ -130,6 +130,7 @@ macro_rules! byte_array_type {
                 Self { bytes }
             }
 
+            // Big endian bit ordering
             pub fn bit(&self, index: usize) -> bool {
                 assert!(index < ($size * 8));
                 let bi = index / 8;
@@ -152,6 +153,7 @@ macro_rules! byte_array_type {
                 None
             }
 
+            // Big endian nibble ordering
             pub fn nibble(&self, index: usize) -> u8 {
                 assert!(index < ($size * 2));
                 let bi = index / 2;
diff --git a/veilid-core/src/crypto/tests/test_types.rs b/veilid-core/src/crypto/tests/test_types.rs
index 72813ea0..3a544186 100644
--- a/veilid-core/src/crypto/tests/test_types.rs
+++ b/veilid-core/src/crypto/tests/test_types.rs
@@ -353,10 +353,40 @@ async fn test_operations(vcrypto: CryptoSystemVersion) {
     assert_eq!(d4.first_nonzero_bit(), Some(0));
 }
 
+pub async fn test_crypto_key_ordering() {
+    let k1 = CryptoKey::new([
+        128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0,
+    ]);
+    let k2 = CryptoKey::new([
+        1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0,
+    ]);
+    let k3 = CryptoKey::new([
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 128,
+    ]);
+    let k4 = CryptoKey::new([
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 1,
+    ]);
+    let k5 = CryptoKey::new([
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0,
+    ]);
+
+    assert!(k2 < k1);
+    assert!(k3 < k2);
+    assert!(k4 < k3);
+    assert!(k5 < k4);
+}
+
 pub async fn test_all() {
     let api = crypto_tests_startup().await;
     let crypto = api.crypto().unwrap();
 
+    test_crypto_key_ordering().await;
+
     // Test versions
     for v in VALID_CRYPTO_KINDS {
         let vcrypto = crypto.get(v).unwrap();
diff --git a/veilid-core/src/routing_table/find_peers.rs b/veilid-core/src/routing_table/find_peers.rs
index 54a45796..2373e9be 100644
--- a/veilid-core/src/routing_table/find_peers.rs
+++ b/veilid-core/src/routing_table/find_peers.rs
@@ -51,6 +51,7 @@ impl RoutingTable {
             return NetworkResult::invalid_message("unsupported cryptosystem");
         };
         let own_distance = vcrypto.distance(&own_node_id.value, &key.value);
+        let vcrypto2 = vcrypto.clone();
 
         let filter = Box::new(
             move |rti: &RoutingTableInner, opt_entry: Option<Arc<BucketEntry>>| {
@@ -98,6 +99,46 @@ impl RoutingTable {
             },
         );
 
+        // xxx test
+        // Validate peers returned are, in fact, closer to the key than the node we sent this to
+        let valid = match Self::verify_peers_closer(vcrypto2, own_node_id, key, &closest_nodes) {
+            Ok(v) => v,
+            Err(e) => {
+                panic!("missing cryptosystem in peers node ids: {}", e);
+            }
+        };
+        if !valid {
+            panic!("non-closer peers returned");
+        }
+
         NetworkResult::value(closest_nodes)
     }
+
+    /// Determine if set of peers is closer to key_near than key_far
+    pub(crate) fn verify_peers_closer(
+        vcrypto: CryptoSystemVersion,
+        key_far: TypedKey,
+        key_near: TypedKey,
+        peers: &[PeerInfo],
+    ) -> EyreResult<bool> {
+        let kind = vcrypto.kind();
+
+        if key_far.kind != kind || key_near.kind != kind {
+            bail!("keys all need the same cryptosystem");
+        }
+
+        let mut closer = true;
+        for peer in peers {
+            let Some(key_peer) = peer.node_ids().get(kind) else {
+                bail!("peers need to have a key with the same cryptosystem");
+            };
+            let d_near = vcrypto.distance(&key_near.value, &key_peer.value);
+            let d_far = vcrypto.distance(&key_far.value, &key_peer.value);
+            if d_far < d_near {
+                closer = false;
+            }
+        }
+
+        Ok(closer)
+    }
 }
diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs
index eb57b02f..f9b41323 100644
--- a/veilid-core/src/rpc_processor/mod.rs
+++ b/veilid-core/src/rpc_processor/mod.rs
@@ -404,37 +404,6 @@ impl RPCProcessor {
         routing_table.signed_node_info_is_valid_in_routing_domain(routing_domain, &signed_node_info)
     }
 
-    /// Determine if set of peers is closer to key_near than key_far
-    fn verify_peers_closer(
-        &self,
-        vcrypto: CryptoSystemVersion,
-        key_far: TypedKey,
-        key_near: TypedKey,
-        peers: &[PeerInfo],
-    ) -> Result<bool, RPCError> {
-        let kind = vcrypto.kind();
-
-        if key_far.kind != kind || key_near.kind != kind {
-            return Err(RPCError::internal("keys all need the same cryptosystem"));
-        }
-
-        let mut closer = true;
-        for peer in peers {
-            let Some(key_peer) = peer.node_ids().get(kind) else {
-                return Err(RPCError::invalid_format(
-                    "peers need to have a key with the same cryptosystem",
-                ));
-            };
-            let d_near = vcrypto.distance(&key_near.value, &key_peer.value);
-            let d_far = vcrypto.distance(&key_far.value, &key_peer.value);
-            if d_far < d_near {
-                closer = false;
-            }
-        }
-
-        Ok(closer)
-    }
-
     //////////////////////////////////////////////////////////////////////
 
     /// Search the DHT for a single node closest to a key and add it to the routing table and return the node reference
diff --git a/veilid-core/src/rpc_processor/rpc_get_value.rs b/veilid-core/src/rpc_processor/rpc_get_value.rs
index 352eb192..cd82f067 100644
--- a/veilid-core/src/rpc_processor/rpc_get_value.rs
+++ b/veilid-core/src/rpc_processor/rpc_get_value.rs
@@ -76,15 +76,13 @@ impl RPCProcessor {
         let (value, peers, descriptor) = get_value_a.destructure();
 
         // Validate peers returned are, in fact, closer to the key than the node we sent this to
-        let valid = match self.verify_peers_closer(vcrypto, target_node_id, key, &peers) {
+        let valid = match RoutingTable::verify_peers_closer(vcrypto, target_node_id, key, &peers) {
             Ok(v) => v,
             Err(e) => {
-                if matches!(e, RPCError::Internal(_)) {
-                    return Err(e);
-                }
-                return Ok(NetworkResult::invalid_message(
-                    "missing cryptosystem in peers node ids",
-                ));
+                return Ok(NetworkResult::invalid_message(format!(
+                    "missing cryptosystem in peers node ids: {}",
+                    e
+                )));
             }
         };
         if !valid {
diff --git a/veilid-core/src/rpc_processor/rpc_set_value.rs b/veilid-core/src/rpc_processor/rpc_set_value.rs
index a12ae922..e36852af 100644
--- a/veilid-core/src/rpc_processor/rpc_set_value.rs
+++ b/veilid-core/src/rpc_processor/rpc_set_value.rs
@@ -83,15 +83,13 @@ impl RPCProcessor {
         let (set, value, peers) = set_value_a.destructure();
 
         // Validate peers returned are, in fact, closer to the key than the node we sent this to
-        let valid = match self.verify_peers_closer(vcrypto, target_node_id, key, &peers) {
+        let valid = match RoutingTable::verify_peers_closer(vcrypto, target_node_id, key, &peers) {
             Ok(v) => v,
             Err(e) => {
-                if matches!(e, RPCError::Internal(_)) {
-                    return Err(e);
-                }
-                return Ok(NetworkResult::invalid_message(
-                    "missing cryptosystem in peers node ids",
-                ));
+                return Ok(NetworkResult::invalid_message(format!(
+                    "missing cryptosystem in peers node ids: {}",
+                    e
+                )));
             }
         };
         if !valid {
diff --git a/veilid-core/src/storage_manager/mod.rs b/veilid-core/src/storage_manager/mod.rs
index dbef0bc8..56a906bc 100644
--- a/veilid-core/src/storage_manager/mod.rs
+++ b/veilid-core/src/storage_manager/mod.rs
@@ -341,7 +341,6 @@ impl StorageManager {
         } else {
             ValueData::new(data, writer.key)
         };
-        let seq = value_data.seq();
 
         // Validate with schema
         if !schema.check_subkey_value_data(descriptor.owner(), subkey, &value_data) {
@@ -374,7 +373,6 @@ impl StorageManager {
         drop(inner);
 
         // Use the safety selection we opened the record with
-
         let final_signed_value_data = self
             .outbound_set_value(
                 rpc_processor,
@@ -386,13 +384,12 @@ impl StorageManager {
             )
             .await?;
 
-        // If we got a new value back then write it to the opened record
-        if final_signed_value_data.value_data().seq() != seq {
-            let mut inner = self.lock().await?;
-            inner
-                .handle_set_local_value(key, subkey, final_signed_value_data.clone())
-                .await?;
-        }
+        // Whatever record we got back, store it locally, might be newer than the one we asked to save
+        let mut inner = self.lock().await?;
+        inner
+            .handle_set_local_value(key, subkey, final_signed_value_data.clone())
+            .await?;
+
         Ok(Some(final_signed_value_data.into_value_data()))
     }
 
diff --git a/veilid-python/tests/test_dht.py b/veilid-python/tests/test_dht.py
index 630a6d12..af17bc64 100644
--- a/veilid-python/tests/test_dht.py
+++ b/veilid-python/tests/test_dht.py
@@ -67,6 +67,9 @@ async def test_set_get_dht_value(api_connection: veilid.VeilidAPI):
         vd2 = await rc.get_dht_value(rec.key, 0, False)
         assert vd2 != None
         
+        print("vd: {}", vd.__dict__)
+        print("vd2: {}", vd2.__dict__)
+
         assert vd == vd2
 
         await rc.close_dht_record(rec.key)
diff --git a/veilid-python/veilid/types.py b/veilid-python/veilid/types.py
index d1766fab..7116550e 100644
--- a/veilid-python/veilid/types.py
+++ b/veilid-python/veilid/types.py
@@ -2,6 +2,7 @@ import base64
 import json
 from enum import StrEnum
 from typing import Any, Optional, Self, Tuple
+from functools import total_ordering
 
 ####################################################################
 
@@ -323,6 +324,7 @@ class DHTRecordDescriptor:
         return self.__dict__
 
 
+# @total_ordering
 class ValueData:
     seq: ValueSeqNum
     data: bytes
@@ -333,6 +335,21 @@ class ValueData:
         self.data = data
         self.writer = writer
 
+    # def __lt__(self, other):
+    #     return self.data < other.data
+
+    # def __eq__(self, other):
+    #     return self.cgpa == other.cgpa
+
+    # def __le__(self, other):
+    #     return self.cgpa<= other.cgpa
+
+    # def __ge__(self, other):
+    #     return self.cgpa>= other.cgpa
+
+    # def __ne__(self, other):
+    #     return self.cgpa != other.cgpa
+
     @classmethod
     def from_json(cls, j: dict) -> Self:
         return cls(