fix(mesh): refresh federation chat names + roster after sync without restart (#42)

A peer accepted via invite is seeded into the mesh peer table with
name=None, so it shows as "Archipelago <pubkey8>" in chat. Federation
sync later learns the real name (update_node_state writes it to
nodes.json) and discovers transitive peers (merge_transitive_peers),
but nothing pushed those into the live mesh peer table — the chat list
stayed stale until the next mesh restart, and transitive peers never
appeared as contacts at all.

Add RpcHandler::refresh_federation_mesh_peers() (re-runs the idempotent,
onion-deduped seed_federation_peers_into_mesh) and call it after every
periodic sync cycle (server.rs) and after the manual federation.sync-all
RPC. Names now correct themselves and the full roster meshes within a
sync cycle, no restart needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-06-17 05:52:41 -04:00
parent 06cf80d4a2
commit 6de8173d18
2 changed files with 28 additions and 0 deletions

View File

@ -30,6 +30,25 @@ impl RpcHandler {
mesh::upsert_federation_peer(&svc.shared_state(), pubkey_hex, did, name).await; mesh::upsert_federation_peer(&svc.shared_state(), pubkey_hex, did, name).await;
} }
} }
/// Re-seed every federation node from disk into the mesh peer table so the
/// chat list reflects what the latest federation sync learned — display
/// names (landed in `nodes.json` by `update_node_state` when a peer
/// announces its name) and transitively-discovered peers (merged by
/// `merge_transitive_peers`) — WITHOUT waiting for a mesh restart.
///
/// Without this, a peer accepted via invite (seeded with `name = None`)
/// stays "Archipelago <pubkey8>" in chat until the next restart even after
/// sync has learned its real name, and transitive peers never appear as
/// chat contacts at all. `seed_federation_peers_into_mesh` is idempotent
/// and dedups by onion, so calling it after each sync is safe.
/// Best-effort: silently no-ops when mesh is off.
pub(crate) async fn refresh_federation_mesh_peers(&self) {
let svc = self.mesh_service.read().await;
if let Some(svc) = svc.as_ref() {
mesh::seed_federation_peers_into_mesh(&svc.shared_state(), &self.config.data_dir).await;
}
}
} }
impl RpcHandler { impl RpcHandler {
@ -341,6 +360,10 @@ impl RpcHandler {
} }
} }
// Push any names/roster the sync just learned into the live mesh peer
// table so the chat list updates without a restart (#42).
self.refresh_federation_mesh_peers().await;
Ok(serde_json::json!({ Ok(serde_json::json!({
"synced": synced, "synced": synced,
"failed": failed, "failed": failed,

View File

@ -508,6 +508,7 @@ impl Server {
{ {
let data_dir = config.data_dir.clone(); let data_dir = config.data_dir.clone();
let state = state_manager.clone(); let state = state_manager.clone();
let rpc = api_handler.rpc_handler().clone();
tokio::spawn(async move { tokio::spawn(async move {
// First run 60s after boot to let onboarding settle. // First run 60s after boot to let onboarding settle.
tokio::time::sleep(Duration::from_secs(60)).await; tokio::time::sleep(Duration::from_secs(60)).await;
@ -558,6 +559,10 @@ impl Server {
} }
tokio::time::sleep(Duration::from_secs(5)).await; tokio::time::sleep(Duration::from_secs(5)).await;
} }
// After syncing every peer, push the names/roster just
// learned (into nodes.json) into the live mesh peer table
// so chat contacts refresh without a restart (#42).
rpc.refresh_federation_mesh_peers().await;
} }
}); });
} }