From bf10aeb74d50170ee2ddaea26b4dfa1459bf1b33 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 16 Sep 2026 23:24:18 -0600 Subject: [PATCH] test(TASK-047): split the ntfs short-name guard test by platform reality On NTFS the fixture self-collides: .builtin-manifest auto-generates the 8.3 short name BUILTI~1, so writing that fixture file opens the manifest through its alias and clobbers it -- the exact threat the guard models. Gate the fixture-based test to unix, and add a windows counterpart that proves the collision where 8.3 generation is enabled and asserts the filter invariant (alias-shaped entries are neither removal candidates nor claimed) either way. --- src/config/builtin_manifest.rs | 60 ++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/config/builtin_manifest.rs b/src/config/builtin_manifest.rs index ce0da9c..abd616e 100644 --- a/src/config/builtin_manifest.rs +++ b/src/config/builtin_manifest.rs @@ -320,6 +320,15 @@ mod tests { assert!(!is_ntfs_short_name_alias(".builtin-manifest")); } + /// This fixture is unrealizable on NTFS: `.builtin-manifest` gets an + /// auto-generated 8.3 short name of exactly this shape (`BUILTI~1`), so + /// writing `dir/BUILTI~1` opens the manifest through its alias and + /// clobbers it instead of creating a distinct file — the precise + /// collision [`is_ntfs_short_name_alias`] defends against. Only + /// filesystems without 8.3 aliasing can host alias-shaped names as real, + /// distinct files; Windows coverage lives in + /// `ntfs_short_name_entries_never_accepted_on_windows` below. + #[cfg(unix)] #[test] fn ntfs_short_name_entries_never_direct_deletions() { let dir = fresh_dir("builtin-manifest-shortname-"); @@ -335,4 +344,55 @@ mod tests { ); let _ = fs::remove_dir_all(&dir); } + + /// Windows counterpart of `ntfs_short_name_entries_never_direct_deletions`: + /// alias-shaped files cannot exist as distinct fixtures here, so the guard + /// invariant is proven through the manifest filter instead. Only entries + /// accepted into the previous-manifest set can become removal candidates + /// (`previous.difference(shipped)`) or be claimed as owned + /// (`shipped ∩ previous`); listing the alias names in `shipped` makes the + /// second path observable in the rewritten manifest — a broken guard + /// would claim them. Where the volume generates 8.3 short names, the + /// self-collision that makes the unix fixture unrealizable is also + /// asserted directly; where generation is disabled, the alias simply + /// never materializes and the invariant assertions stand on their own. + #[cfg(windows)] + #[test] + fn ntfs_short_name_entries_never_accepted_on_windows() { + let dir = fresh_dir("builtin-manifest-shortname-win-"); + fs::write( + dir.join(BUILTIN_MANIFEST_FILE), + "BUILTI~1\nbuilti~1.sh\nkept.sh\n", + ) + .unwrap(); + fs::write(dir.join("kept.sh"), "current").unwrap(); + + let alias = dir.join("BUILTI~1"); + if alias.exists() { + assert_eq!( + fs::read_to_string(&alias).unwrap(), + fs::read_to_string(dir.join(BUILTIN_MANIFEST_FILE)).unwrap(), + "the 8.3 alias opens the manifest itself, not a distinct file" + ); + } + + reconcile_builtin_dir( + &dir, + &shipped(&["BUILTI~1", "builti~1.sh", "kept.sh"]), + &shipped(&[]), + ) + .unwrap(); + + assert!(dir.join("kept.sh").exists(), "real shipped file untouched"); + assert!( + dir.join(BUILTIN_MANIFEST_FILE).exists(), + "manifest survives reconciliation" + ); + assert_eq!( + fs::read_to_string(dir.join(BUILTIN_MANIFEST_FILE)).unwrap(), + "kept.sh\n", + "alias-shaped entries are never accepted: neither removal candidates nor claimed" + ); + let _ = fs::remove_dir_all(&dir); + } }