Found during the SFTP backend build (Task 4 review, 2026-08-03): the SFTP resumable-completion path had a data-loss window that was fixed there (d969fd8), and independent review confirmed driven-localfs has the identical hole, arguably worse (silent atomic replace, no remove step).
Sequence (crash-then-adopt-then-resume), bites on any case-insensitive/normalizing filesystem - default APFS and NTFS, both first-class targets:
resumable_session for Foo.txt creates the temp and claims the name in-process only - claim keys are to_ascii_lowercase (layout.rs:207) and no sidecar is written until commit.
- Process crashes; the in-process claim is gone.
- A later
create for foo.txt adopts the path: ownership() returns Ours when no sidecar exists, warn-only (layout.rs:277-294).
- The parked session is hydrated:
store.rs:1011-1012 calls layout::claim_exact(...) blind - no sidecar/ownership probe on that path.
- Completion commits via
commit_rename = std::fs::rename (store.rs:423 -> fsx.rs:100-101), which silently REPLACES the destination - destroying the object created in step 3.
Fix shape (proven in driven-sftp): on hydration, re-check destination ownership against the sidecar and return SessionInvalid on mismatch (see crates/driven-sftp/src/store.rs hydrate_session's ownership re-check and its mutation test a_parked_session_never_removes_an_object_that_became_someone_elses). Port the same discipline to localfs; a torn/unparseable sidecar mapping to Ok(None) re-opens the window and should fail closed on that path too.
Found by: SFTP Task 4 implementer, independently verified with citations by the task reviewer.
Found during the SFTP backend build (Task 4 review, 2026-08-03): the SFTP resumable-completion path had a data-loss window that was fixed there (d969fd8), and independent review confirmed driven-localfs has the identical hole, arguably worse (silent atomic replace, no remove step).
Sequence (crash-then-adopt-then-resume), bites on any case-insensitive/normalizing filesystem - default APFS and NTFS, both first-class targets:
resumable_sessionforFoo.txtcreates the temp and claims the name in-process only - claim keys areto_ascii_lowercase(layout.rs:207) and no sidecar is written until commit.createforfoo.txtadopts the path:ownership()returnsOurswhen no sidecar exists, warn-only (layout.rs:277-294).store.rs:1011-1012callslayout::claim_exact(...)blind - no sidecar/ownership probe on that path.commit_rename=std::fs::rename(store.rs:423->fsx.rs:100-101), which silently REPLACES the destination - destroying the object created in step 3.Fix shape (proven in driven-sftp): on hydration, re-check destination ownership against the sidecar and return
SessionInvalidon mismatch (seecrates/driven-sftp/src/store.rshydrate_session's ownership re-check and its mutation testa_parked_session_never_removes_an_object_that_became_someone_elses). Port the same discipline to localfs; a torn/unparseable sidecar mapping toOk(None)re-opens the window and should fail closed on that path too.Found by: SFTP Task 4 implementer, independently verified with citations by the task reviewer.