Skip to content

fix(core): verify a tree staged before sidecars the way it was sealed - #235

Open
FeathBow wants to merge 2 commits into
mainfrom
fix/undo-a-tree-staged-before-sidecars
Open

FeathBow wants to merge 2 commits into
mainfrom
fix/undo-a-tree-staged-before-sidecars

Conversation

@FeathBow

@FeathBow FeathBow commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

A tree that v0.1.7 staged cannot be undone or purged by main. v0.1.7 wrote its WAL at v11, before held-tree sidecars, so the v3 content manifest of each tree it staged has no sidecar behind it. Since #90, recovery sends every v3 manifest to the streamed verifier, which reads the published sidecar. A v3 staging without one fails as UndoManifestChanged and is recorded RecoveryRequired, as if its baseline had been lost. From then on every clean, undo and purge refuses to run until the store is recovered by hand. A clean purges trash entries seven days old through the same admission, so an upgraded account reaches this without asking for it.

Recovery now streams a v3 manifest only when the WAL replayed the staging at v12 or later, the rule the WAL already applies when it requires a sidecar before TreeSealed. An earlier staging is verified by the resident walk it was sealed against. This applies at all three places that verify a staged tree: verified undo, purge admission, and verification of a staging found after a restart. A v12 or later staging whose sidecar is missing or corrupt is refused as before.

Restored modes. The resident walk checked each directory's mode against the sealed one only before undo restored it. Once the modes were restored, it replaced every directory's mode with the sealed one and compared the digest, so a directory whose mode changed after the restore still passed and was renamed back. It now requires each directory to be at the mode undo restored before it replaces it, as the streamed path does. Stagings with v1 and v2 manifests use the same walk and get the same check.

How it was found. Preparing the next release, I staged with the v0.1.7 release binary and undid with main in an ext4 chroot on Ubuntu 24.04, x86_64. The undo failed on a plain pip cache, and bisecting 6d6ceda..d1db13a with the same check gave bc8138a (#90) as the first bad commit.

Not changed. v0.1.7 still cannot open a WAL that a later version has written to; it reports unsupported legacy WAL version 13. Moving back is not supported, and this does not change that.

Validation

Passed locally on this branch:

cargo fmt --all --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo clippy --workspace --all-targets --locked --target x86_64-unknown-linux-musl -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --locked --document-private-items
cargo deny check
git diff --check origin/main..HEAD
cargo test --workspace --locked

The suite is at 1826 passed / 0 failed / 1 ignored.

  • undo_before_sidecars, purge_before_sidecars, verify_before_sidecars: a staging is rewritten as v0.1.7 would have left it, with every frame at v11, the manifest record without the sidecar commitment and the sidecar removed. It is then undone, purged, and verified after a restart. On main the first two fail with the errors above and the third does not verify.
  • drift_before_sidecars: a changed tree from before sidecars is refused, and the staging stays VerifiedCommitted and undoable instead of being recorded as a lost baseline.
  • drift_after_restore: once undo has restored a directory to 0o770, it is changed to 0o700 before the tree is verified. The undo stops at UndoModesRestored without renaming the tree back, for a staging from before sidecars and for one with a sidecar. Before the second commit, the staging from before sidecars was renamed back.
  • sidecar_era_boundary: a v11 staging binds no sidecar, while v12 and v13 stagings do. main wrote v12 from feat(core): bind held-tree sidecars to WAL recovery #87 to perf(core): record a purge's progress once, at its end #219, so a store from those builds has sidecars to check.

Thirteen mutations, all caught on the final code. Nine are on the dispatch: binding a sidecar always or never, moving the boundary to 11 or 13, reverting each of the four call sites to the schema check alone, and dropping the version from the streaming check. Never binding a sidecar also fails ten existing tests of the streamed path, among them the ones that refuse a corrupt sidecar. Four are on the restored-mode check: always expecting the sealed mode, always expecting the original one, swapping the two, and leaving the mode out of the comparison.

Across versions, in the same chroot, with the v0.1.7 release (x86_64-unknown-linux-musl) staging and a static build of this branch's head undoing or purging. Each case ran in a new account, and "identical" compares every path's type, mode, link count, size, symlink target, content hash and user.* xattrs before and after.

Case main at d1db13a this branch
v0.1.7 stages a pip cache, undo fails, store blocked restored, identical
the same with an internal hard link, a symlink, a file xattr or a 0700 directory fails restored, identical
v0.1.7 stages, trash purge fails, store blocked purged
v0.1.7 stages pip, the newer build stages and undoes a project's target, then undoes pip both restored, identical
v0.1.7 stages 822 directories in 10.2 s, undo restored, identical
v0.1.7 killed at 15, 30, 50, 70 and 90% of that time, then undo rolled back when killed while sealing, restored when killed after the rename; identical each time

Safety

  • The version comes from the frame the WAL replayed for the staging, which the frame checksum covers, and the WAL already refuses a v12 or later tree without a sidecar before TreeSealed.
  • The resident walk compares the same durable manifest digest. Streaming exists to bound memory; the resident walk runs within the default inventory limits, and v0.1.7 staged at most 1,023 directories.
  • A failure on the resident path is treated as live-tree drift, as it is for a v12 staging, so the staging stays undoable. A missing or corrupt sidecar on a v12 or later staging is still recorded RecoveryRequired.

@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6674bbc1-86b1-41ac-8e49-6edea24c3352
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant