microscope_memory/
doctor.rs1use crate::config::Config;
5use crate::merkle::MerkleTree;
6use crate::reader::MicroscopeReader;
7use colored::Colorize;
8use std::fs;
9use std::path::Path;
10
11pub fn run_doctor(config: &Config, fix: bool) -> Result<(), String> {
12 println!("{}", "=".repeat(60));
13 println!(
14 " {}",
15 "MICROSCOPE MEMORY DOCTOR — Integrity Scan".cyan().bold()
16 );
17 println!("{}", "=".repeat(60));
18
19 let output_dir = Path::new(&config.paths.output_dir);
20 let mut artifacts_missing = false;
21
22 let core_files = ["meta.bin", "microscope.bin", "data.bin"];
24 for file in &core_files {
25 let p = output_dir.join(file);
26 if !p.exists() {
27 println!(" [{}] {} is missing", "ERR".red(), file);
28 artifacts_missing = true;
29 } else {
30 println!(
31 " [{}] {} present ({:.1} KB)",
32 "OK".green(),
33 file,
34 fs::metadata(&p)
35 .map(|m| m.len() as f64 / 1024.0)
36 .unwrap_or(0.0)
37 );
38 }
39 }
40
41 if artifacts_missing {
42 println!(
43 "\n {} Core artifacts are missing. Run 'build' to regenerate.",
44 "WARN:".yellow()
45 );
46 return Ok(());
47 }
48
49 let reader = MicroscopeReader::open(config)?;
51 println!(
52 "\n {} scanning {} blocks...",
53 "INTEGRITY:".yellow(),
54 reader.block_count
55 );
56
57 let mut bad_crc = 0;
58 for i in 0..reader.block_count {
59 let h = reader.header(i);
60 let stored_crc = u16::from_le_bytes(h.crc16);
61 if stored_crc == 0 {
62 continue;
63 }
64
65 let start = h.data_offset as usize;
66 let end = start + h.data_len as usize;
67 if end > reader.data.len() {
68 bad_crc += 1;
69 continue;
70 }
71
72 let computed = crate::crc16_ccitt(&reader.data[start..end]);
73 if computed != stored_crc {
74 bad_crc += 1;
75 }
76 }
77
78 if bad_crc == 0 {
79 println!(" [{}] All block CRCs verified.", "OK".green());
80 } else {
81 println!(
82 " [{}] {} block(s) have CRC mismatches!",
83 "FAIL".red(),
84 bad_crc
85 );
86 }
87
88 let merkle_path = output_dir.join("merkle.bin");
90 if merkle_path.exists() {
91 if let Ok(merkle_data) = fs::read(&merkle_path) {
92 if let Some(tree) = MerkleTree::from_bytes(&merkle_data) {
93 println!(
94 " [{}] Merkle tree loaded (Root: {})",
95 "OK".green(),
96 crate::hex_str(&tree.root)
97 );
98 }
99 }
100 }
101
102 println!("\n {} checking append log...", "RECOVERY:".yellow());
104 let append_path = output_dir.join("append.bin");
105 if append_path.exists() {
106 let data = fs::read(&append_path).map_err(|e| e.to_string())?;
107 if data.is_empty() {
108 println!(" [{}] Append log is empty.", "OK".green());
109 } else {
110 let mut pos = 0;
111 let (is_v2, is_v3) = if data.len() >= 4 {
112 (&data[0..4] == b"APv2", &data[0..4] == b"APv3")
113 } else {
114 (false, false)
115 };
116 if is_v2 || is_v3 {
117 pos = 4;
118 }
119 let header_size = if is_v3 {
120 35
121 } else if is_v2 {
122 19
123 } else {
124 18
125 };
126
127 let mut valid_pos = pos;
128 let mut count = 0;
129 let mut corrupted = false;
130
131 while pos + header_size <= data.len() {
132 let len = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()) as usize;
133 if pos + header_size + len > data.len() {
134 corrupted = true;
135 break;
136 }
137 pos += header_size + len;
138 valid_pos = pos;
139 count += 1;
140 }
141
142 if pos < data.len() {
143 corrupted = true;
144 }
145
146 if corrupted {
147 println!(
148 " [{}] Found corrupted data at tail. Valid entries: {}",
149 "WARN".yellow(),
150 count
151 );
152 if fix {
153 println!(
154 " [{}] Truncating append.bin to last valid position ({} bytes)...",
155 "FIX".green(),
156 valid_pos
157 );
158 let f = fs::OpenOptions::new()
159 .write(true)
160 .open(&append_path)
161 .map_err(|e| e.to_string())?;
162 f.set_len(valid_pos as u64).map_err(|e| e.to_string())?;
163 let sidecar = output_dir.join(crate::embedding_index::APPEND_EMBEDDINGS_FILE);
167 if sidecar.exists() {
168 fs::remove_file(&sidecar).map_err(|e| e.to_string())?;
169 println!(
170 " [{}] Removed {} (its positions no longer exist).",
171 "DONE".green(),
172 crate::embedding_index::APPEND_EMBEDDINGS_FILE
173 );
174 }
175 println!(" [{}] Truncation successful.", "DONE".green());
176 } else {
177 println!(
178 " [{}] Run with --fix to truncate and recover the log.",
179 "INFO".cyan()
180 );
181 }
182 } else {
183 println!(
184 " [{}] Append log is healthy. Entries: {}",
185 "OK".green(),
186 count
187 );
188 }
189 }
190 } else {
191 println!(" [{}] No append log found.", "INFO".cyan());
192 }
193
194 println!("\n{}", "Status: Diagnostics complete.".bold());
195 println!("{}", "=".repeat(60));
196
197 Ok(())
198}