From e1ca32eb7748ec7d0abd08dda2eaa1bea0cd8afc Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sun, 16 Aug 2026 08:23:18 +0800 Subject: [PATCH] docs(config): the quarantine cap wraps, and the numbered copies never move MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `quarantine_path` ends in candidates.find(|c| !c.exists()).unwrap_or(base) which reads as a defensive branch for a case that cannot happen. It is the wrap. Eight is a cap on how much of a repeatedly-corrupted file's history a config directory may accumulate, and once every name is taken the oldest copy is the one overwritten. The part worth writing down is what that leaves behind: nothing rotates the numbered names, so from the cap onward `.corrupt.1` through `.corrupt.7` hold the second through eighth corruptions forever and only the base name moves. Read as a timeline — which is how someone recovering a tree would read it — that is the opposite of what it looks like. Pinned rather than asserted: nine corruptions, eight files left, the ninth landing on the base, and the base holding the ninth's contents. Found by sweeping numeric constants for ones whose *value* is load-bearing. Most of the 367 undocumented ones are self-evident (font size bounds, test timeouts); this was the one where the number decides what a user can still recover. --- crates/tty7-core/src/core/config.rs | 64 +++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/crates/tty7-core/src/core/config.rs b/crates/tty7-core/src/core/config.rs index e071896e..f688d939 100644 --- a/crates/tty7-core/src/core/config.rs +++ b/crates/tty7-core/src/core/config.rs @@ -929,6 +929,17 @@ fn quarantine_candidates(path: &std::path::Path) -> impl Iterator PathBuf { let mut candidates = quarantine_candidates(path); let base = candidates.next().expect("the base name is always offered"); @@ -1159,6 +1170,59 @@ where mod tests { use super::*; + /// What a ninth corruption does to the eight kept copies. + /// + /// `quarantine_path` ends in `unwrap_or(base)`, which reads like a + /// defensive branch and is the wrap: the cap is on how much history a + /// config directory may accumulate, and the oldest copy is what gives way. + /// The numbered names never rotate, so anyone reading them as a timeline + /// after the cap would be reading the second-to-eighth corruptions and a + /// base that has been overwritten however many times since. + #[test] + fn a_ninth_corruption_overwrites_the_oldest_kept_copy() { + let dir = std::env::temp_dir().join(format!("tty7-quar-{}", std::process::id())); + std::fs::create_dir_all(&dir).expect("scratch dir"); + let path = dir.join("machine.json"); + + let mut used = Vec::new(); + for n in 0..9 { + std::fs::write(&path, format!("corruption {n}")).expect("write the file"); + let aside = quarantine_path(&path); + used.push( + aside + .file_name() + .and_then(|n| n.to_str()) + .expect("a utf-8 name") + .to_string(), + ); + quarantine_by_rename(&path); + } + + assert_eq!( + used[0], "machine.json.corrupt", + "the first goes to the base" + ); + assert_eq!(used[1], "machine.json.corrupt.1"); + assert_eq!(used[7], "machine.json.corrupt.7", "eight distinct slots"); + assert_eq!( + used[8], "machine.json.corrupt", + "the ninth wraps onto the oldest rather than growing the directory" + ); + + let kept = std::fs::read_dir(&dir) + .expect("read the dir") + .filter_map(Result::ok) + .filter(|e| e.file_name().to_string_lossy().contains(".corrupt")) + .count(); + assert_eq!(kept, 8, "the cap holds: nine corruptions, eight files"); + + let base = std::fs::read_to_string(path.with_extension("json.corrupt")) + .expect("the base copy survives"); + assert_eq!(base, "corruption 8", "and holds the newest, not the first"); + + std::fs::remove_dir_all(&dir).ok(); + } + #[test] fn profile_usage_score_ranks_frequency_and_recency() { let now = 100_000_000u64;