lean_ctx/core/
binary_identity.rs1use std::path::PathBuf;
8use std::sync::OnceLock;
9use std::time::SystemTime;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
14struct Identity {
15 path: PathBuf,
16 len: u64,
17 modified: Option<SystemTime>,
18 inode: u64,
19}
20
21static AT_START: OnceLock<Option<Identity>> = OnceLock::new();
22
23fn current() -> Option<Identity> {
24 let path = std::env::current_exe().ok()?;
25 let meta = std::fs::metadata(&path).ok()?;
26 #[cfg(unix)]
27 let inode = std::os::unix::fs::MetadataExt::ino(&meta);
28 #[cfg(not(unix))]
29 let inode = 0;
30 Some(Identity {
31 path,
32 len: meta.len(),
33 modified: meta.modified().ok(),
34 inode,
35 })
36}
37
38pub fn record_start() {
40 let _ = AT_START.get_or_init(current);
41}
42
43#[must_use]
46pub fn replaced_note() -> Option<String> {
47 let start = AT_START.get()?.as_ref()?;
48 note_for(start, ¤t()?)
49}
50
51fn note_for(start: &Identity, now: &Identity) -> Option<String> {
52 (start != now).then(|| {
53 "Note: lean-ctx was updated on disk after this MCP server started; it still runs the \
54 old engine. Reconnect the MCP server (Claude Code: /mcp, then reconnect lean-ctx) to \
55 use the new one. A reconnect gives this session a new bus id: register your role \
56 again and repost your open claims under the new id.\n"
57 .to_string()
58 })
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 fn identity(inode: u64, len: u64) -> Identity {
66 Identity {
67 path: PathBuf::from("/usr/local/bin/lean-ctx"),
68 len,
69 modified: Some(SystemTime::UNIX_EPOCH),
70 inode,
71 }
72 }
73
74 #[test]
75 fn a_replaced_binary_is_named_and_an_unchanged_one_is_not() {
76 let start = identity(7, 100);
77 assert_eq!(note_for(&start, &identity(7, 100)), None);
78 let replaced = note_for(&start, &identity(8, 100)).expect("new inode");
79 assert!(replaced.contains("Reconnect the MCP server"), "{replaced}");
80 assert!(
81 note_for(&start, &identity(7, 101)).is_some(),
82 "rewritten in place"
83 );
84 }
85
86 #[test]
87 fn the_running_test_binary_matches_itself() {
88 record_start();
89 assert_eq!(replaced_note(), None);
90 }
91}