Finding
In migrate_blocking, every entry yielded by the WalkDir iterator is converted to a fatal HostError and propagated with ?. A single unreadable directory, broken symlink, or permission-denied error therefore aborts the whole function. In move mode the files already processed have been relocated to their canonical destination while the remainder stay at the source, leaving the library split with no record of what moved.
Evidence
crates/archon/src/migrate.rs:78-88 — the per-entry error is made fatal:
for entry in WalkDir::new(source).follow_links(false).into_iter() {
let entry: DirEntry = entry.map_err(|e| {
let path = e.path().unwrap_or(source).to_path_buf();
HostError::MigrateIo {
operation: format!("walk {}", path.display()),
source: e.into_io_error().unwrap_or_else(|| {
std::io::Error::other("walkdir error without underlying IO error")
}),
location: snafu::location!(),
}
})?;
The trailing ? returns out of the loop, so control never reaches the report.errors accounting for an enumeration error.
Why this matters
Move-mode migration is irreversible mid-stream. After an abort the operator holds two partial libraries — some media at the destination, the rest at the source — with no manifest of what moved and no error count (the MigrationReport.errors counter is never incremented for walk errors, since that path is unreachable). On a sovereign device, where a hostile or degraded filesystem (a planted unreadable node, a permission trap) is part of the threat model, a single such node can wedge the entire media library into a silent, inconsistent state; re-running the migration reprocesses survivors but cannot undo the already-moved files if their canonical path changed.
Desired correction
Do not make a WalkDir enumeration error fatal. On an entry error, push a message into report.messages, increment report.errors, and continue to the next entry. Reserve the fatal ? return path for I/O failures on actual file operations (copy/rename), not directory enumeration.
Done when: a migration over a source tree containing an unreadable entry completes with a non-zero report.errors count instead of returning Err, and the migration_skips_non_media_files test is augmented to assert that unreadable entries are counted as errors (not propagated or panicked).
Finding
In
migrate_blocking, every entry yielded by theWalkDiriterator is converted to a fatalHostErrorand propagated with?. A single unreadable directory, broken symlink, or permission-denied error therefore aborts the whole function. In move mode the files already processed have been relocated to their canonical destination while the remainder stay at the source, leaving the library split with no record of what moved.Evidence
crates/archon/src/migrate.rs:78-88— the per-entry error is made fatal:The trailing
?returns out of the loop, so control never reaches thereport.errorsaccounting for an enumeration error.Why this matters
Move-mode migration is irreversible mid-stream. After an abort the operator holds two partial libraries — some media at the destination, the rest at the source — with no manifest of what moved and no error count (the
MigrationReport.errorscounter is never incremented for walk errors, since that path is unreachable). On a sovereign device, where a hostile or degraded filesystem (a planted unreadable node, a permission trap) is part of the threat model, a single such node can wedge the entire media library into a silent, inconsistent state; re-running the migration reprocesses survivors but cannot undo the already-moved files if their canonical path changed.Desired correction
Do not make a
WalkDirenumeration error fatal. On an entry error, push a message intoreport.messages, incrementreport.errors, andcontinueto the next entry. Reserve the fatal?return path for I/O failures on actual file operations (copy/rename), not directory enumeration.Done when: a migration over a source tree containing an unreadable entry completes with a non-zero
report.errorscount instead of returningErr, and themigration_skips_non_media_filestest is augmented to assert that unreadable entries are counted as errors (not propagated or panicked).