fix(metatag-dirs): exact-leaf + descendant path prefix semantics

scan_by_path_prefix now matches BOTH an exact leaf (p == dir) and any
descendant (p.starts_with(dir + '/')), so  returns
 (the leaf itself) as well as  (a child).
Previously a bare dir normalized to 'dir/' and rejected exact leaves.

Also fixes the unit test to assert the corrected semantics (exact leaf
answers its own parent query).

Falls out of wiring the OS migration scripts: their companion index
records (c200/z090 GNS band) carry path=/cubelinux/os/... metatags, and
the daemon  reconstructs the whole migrated tree from them.
This commit is contained in:
CUBELinux-2
2026-08-13 18:41:22 -04:00
parent 94681bbdc0
commit 11e5ed7fca
+19 -16
View File
@@ -618,19 +618,17 @@ impl<B: CubeBackend> CubeStore<B> {
/// "List a directory": every record whose `path` metatag is *under* the
/// given directory prefix (e.g. `scan_by_path_prefix("/etc")` returns
/// `/etc/passwd`, `/etc/network/interfaces`, ...). The prefix must end
/// with `/` or be empty to mean "all paths". This is the metatag-based
/// equivalent of `readdir` for a nested directory the FS itself cannot
/// represent as a literal inode tree.
/// `/etc/passwd`, `/etc/network/interfaces`, ...). Unlike a real FS, the
/// nesting is a shared prefix on the `path` metatag of several records
/// (the source PDF: "operate on CZYX records and Null-space flags rather
/// than paths and inodes") — there is no half-coordinate directory entry.
///
/// A query matches BOTH an exact leaf (`/etc/hostname`) and any descendant
/// (`/etc/hostname` and `/etc/passwd` both answer `/etc`), because a path
/// is just a string: `/etc/hostname` == dir, and `/etc/passwd` starts with
/// `dir + '/'`.
pub fn scan_by_path_prefix(&self, dir: &str) -> Vec<Czyx> {
let norm = if dir.is_empty() {
""
} else if dir.ends_with('/') {
dir
} else {
// Treat a bare "dir" as "dir/" so "/etc" matches "/etc/passwd".
return self.scan_by_path_prefix(&format!("{dir}/"));
};
let sep = format!("{dir}/");
let mut out: Vec<Czyx> = self
.backend
.keys()
@@ -638,7 +636,7 @@ impl<B: CubeBackend> CubeStore<B> {
.filter(|k| {
self.get_record(k)
.map(|(h, _)| match &h.path {
Some(p) => p != norm && p.starts_with(norm),
Some(p) => p == dir || p.starts_with(&sep),
None => false,
})
.unwrap_or(false)
@@ -844,7 +842,9 @@ mod tests {
vec![Czyx::new(200, 1, 1, 1)]
);
// Directory listing via metatag prefix.
// Directory listing via metatag prefix. Bare "/etc" matches every
// record whose path is "/etc" or starts with "/etc/" (an exact leaf
// like "/etc/hostname" answers "/etc", and "/etc/passwd" is a child).
let etc = store.scan_by_path_prefix("/etc");
assert_eq!(etc.len(), 3);
assert!(etc.contains(&Czyx::new(200, 1, 1, 1)));
@@ -855,8 +855,11 @@ mod tests {
"/usr must not appear under /etc"
);
// Bare prefix (no trailing slash) is normalized to "dir/".
assert_eq!(store.scan_by_path_prefix("/etc").len(), 3);
// An exact leaf answers its own parent directory query.
assert_eq!(
store.scan_by_path_prefix("/etc/hosts"),
vec![Czyx::new(200, 1, 3, 1)]
);
// Empty prefix = all paths.
assert_eq!(store.scan_by_path_prefix("").len(), 4);