Preserve native capture content when hard links split
Change
Native capture could corrupt a snapshot after an editor atomically replaced one name of a captured hard-link group. Both paths retained the old shared SDK identity, so later reconstruction of the unchanged alias could overwrite the changed file. Capturing only the changed path could instead change the bytes visible through an uncaptured alias.
This change compares the complete set of observed old SDK bindings with the current host inode groups before queuing writes. It separates stale or incompletely observed groups and then reconstructs the actual in-scope host links. Complete unchanged groups, including the survivor of an alias removal, retain their existing identity. Host link counts outside the capture root do not join unrelated files.
Detached records retain metadata the host did not observe. Their replacement IDs are derived from the initial authenticated file table, old identity, observed host identity and canonical path, so a mounted checkout can safely retain its authored operation across captures and retries. The existing empty staged-content, resize and sparse-write paths reconstruct the file body. Watch hints concerning an old shared record request a complete rescan.
No public schema or dependency changes.
Validation
- The initial public regression suite on unchanged base
71a32edaa0d939cf393ca1a004f5f9b6430b08f3ran 6 tests: 5 failed on the reported alias/capture behavior and the unchanged-group identity control passed. cargo +1.98.1 test --locked --offline -p acyclic-fs --test native_capture_links -- --nocapture --test-threads=1: 7 passed on the final source, using the repository's default test optimization profile.- Executed
native_mount::lazy::tests::hard_links::host_capture_splits_aliases_created_under_a_retained_operation --exact --nocapture --test-threads=1from the built filesystem unit binary: 1 passed. Both partial and complete captures are exercised within this test. cargo +1.98.1 test --locked --offline -p acyclic-fs --lib --config 'profile.test.package.acyclic-fs.opt-level=0' --config 'profile.test.package.acyclic-fs.codegen-units=256' native_capture:: -- --nocapture --test-threads=1: 33 passed, including the strengthened exact-root reuse property. This focused unit run changes only the filesystem package optimization/codegen settings to fit the available build memory; dependencies retain the default profile.cargo +1.98.1 test --locked --offline -p acyclic-fs --all-features --test native_capture_links -- --nocapture --test-threads=1: 7 passed using the default test optimization profile.cargo +1.98.1 clippy --locked -p acyclic-fs --all-targets --all-features -- -D warnings: blocked in unchangedacyclic-contract-derivesource byneedless_pass_by_valueatauthorization.rs:2andtoo_many_linesatregistration.rs:140(109/100). No warnings were disabled.cargo fmt --all --check: the workspace log reports pre-existing formatting differences in 31 unrelated files. An isolated final-source rustfmt check additionally flags the retained upstreamsparse/sparse_scanmodule ordering innative_capture/mod.rs; the other six changed Rust files pass that check with Rust 1.98.1.git diff --check,node scripts/check-boundaries.mjs,bun scripts/check-js-licenses.mjs, andbuf lint: passed.- Gitleaks 8.30.1 scanned the authored commit (1 commit, 23,733 bytes): no leaks.
node scripts/check-metadata.mjs: blocked by the unchangedfuzz/guest-negative-control.pyretired-runtime check on the base.buf format -d --exit-code: formatting differences in eight unchanged protocol files.
These runtime checks were performed on Linux with one build job and debug information disabled; the full workspace release qualification and native macOS/Windows runs are not claimed here.
The public suite covers either atomic-save ordering, larger split partitions, outside-root aliases, partial capture, unchanged groups, removed aliases and watcher recovery. The adapter regression follows actual lazy promotion, file creation, linking and native capture while retaining the original operation binding. The existing reuse property keeps its exact authenticated-root and canonical-record oracle.
Review follow-up
The added regression exercises deletion of one old alias followed by atomic replacement of the last host name, in both lexical orders and with complete and partial capture. Complete capture removes the deleted binding and retains the replacement bytes. Partial capture leaves the omitted SDK alias at its prior contents and assigns the replacement a separate logical identity. Both paths in the pinned prior generation remain unchanged. A host inode replacement does not, on its own, require a new logical SDK FileId after every old binding is fully observed; the regression checks the user-visible snapshot and alias guarantees directly.
The fixture now uses fallible split_first() before writing the temporary replacement file, so an empty replacement group returns TestResult instead of panicking.
The public test target retains native-mount: its actual seal_checkout import is exported only with that feature. Lowering the gate to native-watch without changing the test API would make that feature combination fail to compile.
Fresh follow-up validation on f1a1fe57f237dd49a3ff562e102a124c52bf28ca:
cargo +1.98.1 test --locked -p acyclic-fs --all-features --test native_capture_links --no-run: passed at normal test optimization (opt-level = 1) on Linux. The resulting exact-source public binary passed all 8 tests, with no failures, ignored tests, or filtered cases.- Rustfmt 1.98.1 for the changed test,
git diff --check, and DCO verification: passed. - Gitleaks 8.30.1 scanned the nonempty follow-up commit: no leaks.
- The follow-up merges cleanly with upstream
06a75f8f537466483ab62f1da7ada6167e016fcd.
The earlier seven-test and 33-unit results above belong to the initial implementation commit; they are not presented as reruns of this follow-up. The prior repository-declared Bun license script result is also limited: its isolated-install discovery gap is tracked separately in #881, so that old exit code does not establish a complete dependency-license audit.
Disclosure
- New imports are recorded in
provenance/manifest.json. Not applicable: no new dependency or imported source. - Generated descriptors contain only public namespaces. Not applicable: no generated descriptor change.
- No private source, protocol, credential, or operational evidence is included.
- Commit includes a DCO sign-off.
- README text written by a human, not generated (only if a README changed). Not applicable: no README change.