Fold doctor into status, and stop id taking the lock

`status` asked a running agent and failed without one; `doctor` checked
the host and ignored the agent. Between them they answered one question
in two halves. They are now one command: device, agent, networks, host
capability, local addresses, all graded and aligned the same way.

`id` was worse than either. It spawned a whole agent to print three
facts, which took the directory lock and so failed with "owned by
another running agent instance" exactly when the answer was most wanted.
The lock exists to keep one writer over the mandatory state; reading who
this device is needs no such thing.

So both commands now prefer the running agent, which is live and
authoritative, and fall back to the state store, which takes no lock.
StateStore gains device_identity(), which reads and never writes:
load_or_create had the side effect of deciding an identity as a
consequence of asking about one.

Presentation moved out of the library. StatusReport::render is gone and
the CLI renders the structured data, so there is one renderer rather than
two that would drift. Health gains an Info level for rows that are facts
rather than checks — an endpoint id is neither good nor bad, and a column
of green next to plain data teaches the eye to ignore the column. A
report with no checks in it now ends without a summary instead of
claiming that everything checked out.

PathAddr and TransportKind grew Display impls; both were reaching the
user through {:?}, which is how "Direct via Ip(88.198.17.44:49792)" got
printed. The transport grading compares without regard to case, because
the agent answering can be a different build from the client asking and
this field's spelling has now changed once.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
tsunagi
2026-09-21 15:07:54 +01:00
co-authored by Claude Opus 5
parent 3e83e33313
commit 8a6fb39689
7 changed files with 543 additions and 237 deletions
+2 -102
View File
@@ -121,7 +121,8 @@ pub struct PeerReport {
pub endpoint_id: String,
/// Hostname it announced, if any.
pub hostname: Option<String>,
/// `Direct`, `Relay` or `Unknown`, as the transport reports it.
/// How the connection reaches the peer: `direct`, `relay` or `unknown`,
/// as the transport reports it.
pub transport: String,
/// Round-trip time in milliseconds, when a path is selected.
pub rtt_ms: Option<u64>,
@@ -186,104 +187,3 @@ impl OverlayPeerReport {
self.handshake_secs_ago.is_some()
}
}
impl StatusReport {
/// Renders the report the way the command line prints it.
pub fn render(&self) -> String {
use std::fmt::Write as _;
let mut out = String::new();
let _ = writeln!(out, "endpoint {}", self.endpoint_id);
let _ = writeln!(out, "hostname {}", self.hostname);
let _ = writeln!(out, "bound {}", self.bound_sockets.join(", "));
if !self.cache_healthy {
let _ = writeln!(out, "cache UNAVAILABLE");
}
for network in &self.networks {
let _ = writeln!(
out,
"\nnetwork {} ({}) {}",
network.name,
network.network_id,
if network.active { "active" } else { "inactive" }
);
if network.peers.is_empty() {
let _ = writeln!(out, " no peers");
}
for peer in &network.peers {
let _ = writeln!(
out,
" peer {} {} {}{}",
&peer.endpoint_id[..10.min(peer.endpoint_id.len())],
peer.hostname.as_deref().unwrap_or("?"),
peer.transport,
match peer.rtt_ms {
Some(rtt) => format!(" rtt {rtt}ms"),
None => String::new(),
}
);
}
if network.dial_failures > 0 || network.handshake_failures > 0 {
let _ = writeln!(
out,
" {} dial failure(s), {} handshake failure(s)",
network.dial_failures, network.handshake_failures
);
}
if let Some(overlay) = &network.overlay {
let up = overlay.peers.iter().filter(|peer| peer.is_up()).count();
let _ = writeln!(
out,
" overlay {} {}/{}{} mtu {} {}/{} tunnel(s) up",
overlay.interface,
overlay.address,
overlay.prefix_len,
match &overlay.address_v4 {
Some(v4) => format!(" and {v4}"),
None => String::new(),
},
overlay.mtu,
up,
overlay.peers.len()
);
for peer in &overlay.peers {
let _ = writeln!(
out,
" {} {}{} {} tx {} rx {}{} {}",
&peer.public_key[..8.min(peer.public_key.len())],
peer.address,
match &peer.address_v4 {
Some(v4) => format!(" / {v4}"),
None => String::new(),
},
match peer.handshake_secs_ago {
Some(secs) => format!("handshake {secs}s ago"),
None => "NOT HANDSHAKEN".to_string(),
},
peer.tx_packets,
peer.rx_packets,
if peer.dropped > 0 {
format!(" DROPPED {}", peer.dropped)
} else {
String::new()
},
peer.path
);
}
if overlay.unroutable_packets > 0 {
let _ = writeln!(
out,
" {} packet(s) to addresses nobody owns{}",
overlay.unroutable_packets,
match &overlay.unroutable_sample {
Some(sample) => format!(", most recently {sample}"),
None => String::new(),
}
);
}
}
}
out
}
}