Skip to content

fix: serve fuse base blocks from the chunk cache and un-vacuum the epoch-lag test - #1300

Merged
FSM1 merged 1 commit into
mainfrom
fix/version-block-chunk-cache-and-epoch-lagged-focus-test
Aug 19, 2026
Merged

fix: serve fuse base blocks from the chunk cache and un-vacuum the epoch-lag test#1300
FSM1 merged 1 commit into
mainfrom
fix/version-block-chunk-cache-and-epoch-lagged-focus-test

Conversation

@FSM1

@FSM1 FSM1 commented Aug 19, 2026

Copy link
Copy Markdown
Owner

What changed

version_block bypasses the chunk cache

OperationCore::version_block (crates/fuse/src/ops.rs) read the base version
straight off engine.read_stream and never touched ChunkCache, so every
sub-block write, every read_pending window, and the whole commit walk
re-fetched base blocks the mount was already holding.

It now consults and fills the cache, with the two hazards the issue named
decided as it asked:

  • The truncate floor. Blocks are cached whole; the per-handle
    base_len clamp is applied at use. A clamped, zero-padded block cached under
    (stream, index) would serve another handle on the same stream a hole its own
    floor never opened.
  • Scan pollution. A new Retain { Hot, Scan } argument says whether a path
    will come back to these bytes. push_version's linear commit walk and the
    write-merge (whose block is spilled on the next line) pass Scan: they read
    through the cache and retain nothing, so neither spends a slot of the 64-block
    budget on a block it will never ask for again. Scan also reads through a new
    ChunkCache::peek, which serves without promoting — otherwise a whole-file
    walk would re-rank a reader's hot blocks behind the blocks it touched once.

ChunkCache::insert now refuses an empty block itself, so the admission policy
has one home instead of a guard duplicated at each call site.

An epoch-lagged focus-folder test that asserted nothing

an_epoch_lagged_focus_folder_rejects_without_raising_abuse
(crates/engine/tests/write_plane.rs) raised the read-epoch floor directly on
the device's floor store. That revoked the cached scope read seed, so
read_seed was None and the focus-folder leg was skipped entirely — no
folder was ever resolved, no EpochBelowFloor rejection ever occurred, and both
assertions held for free.

The harness now drives a real read-plane rotation: rotate_read_epoch
republishes the vault root at the next read epoch under a freshly minted
override seed (via reseal_scope_root, carrying the history link, write plane
standing still). That is what raises the device's floor, and it deposits the new
seed in the same step, so read_seed is Some and the focus leg runs.
photos is not swept, so its own writer keeps publishing at the old epoch and
the leg rejects it below the floor.

A positive control in the same test keeps it honest: sweep_folder republishes
the same children re-sealed at the current epoch, and they render. The leg
was live for both passes, so the first outcome was a gate rejection and not a
leg that never ran.

Gate results

cargo fmt --all --check — clean.
cargo clippy --workspace --all-targets -- -D warnings — clean.

cargo test -p cipherbox-engine -p cipherbox-fuse
  1445 passed; 0 failed across every suite in both crates, including
  write_plane   129 passed
  fuse_op_core   74 passed
  cipherbox-fuse unit  59 passed

Both thread counts for write_plane:

cargo test -p cipherbox-engine --test write_plane
  test result: ok. 129 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.11s

cargo test -p cipherbox-engine --test write_plane -- --test-threads=1
  test result: ok. 129 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.46s

No FUSE flakes observed; no test needed a re-run.

Mutation results

Every new assertion was verified against the un-fixed behaviour.

Mutation Test that fires Detail
Revert version_block to the un-cached read a_sub_block_write_merges_over_the_block_the_read_path_cached fetch count 7 vs 6
" reading_through_pending_writes_fetches_each_base_block_once fetch count 30 vs 18 — the second pass re-fetched all 12 base blocks
Serve the cached block unclamped a_cached_base_block_is_still_clamped_to_the_floor_a_shrink_left the shrink's gap reads the old plaintext instead of zeros
push_version passes Retain::Hot the_commit_walk_leaves_a_readers_hot_blocks_in_the_cache fetch count 30 vs 27 — the walk evicted all three of the reader's blocks

For the vacuous-test fix, the negative control is the one the issue asked for —
invert the behaviour the test pins and it must fail:

  • Flip rejection_verdict's EpochBelowFloor arm (crates/engine/src/net/focus.rs)
    from None to Some(RefreshVerdict::Rejected), making epoch lag attributable:
    the rewritten test fails on an unswept folder is not an attacker. The
    pre-existing test stays green under the identical mutation — the vacuity,
    demonstrated rather than argued.
  • Drop the rotate_read_epoch call and keep everything else: the test fails
    on last-known-good stays pinned, left: ["2026", "2027"] right: ["2026"]
    without the rotation the lagging record is simply adopted, so the rotation is
    load-bearing.

The rejection is specifically the epoch stage, not a laundered unseal failure:
the lagging record is sealed under the retired seed, so an unseal-first gate
would classify it as a trust violation and raise abuse. No abuse event is
raised.

Review passes

/simplify — run on git diff origin/main...HEAD (four parallel passes: reuse,
simplification, efficiency, altitude). Applied:

  • empty-block admission moved into ChunkCache::insert, removing the guard and
    its rationale from both call sites;
  • ChunkCache::peek, because Retain::Scan still promoted through get;
  • the write-merge moved from Hot to Scan — the block it fetches is spilled
    on the next line, so retaining it spent a budget slot for no reuse;
  • a clamp_into helper, so the clamp is written once;
  • rotate_read_epoch now reuses net::author::{ENVELOPE_V, author_scope_root_with_section} and the file's own publish_next_record
    instead of hand-rolling seal → encode → publish. The fixture root now passes
    the same release-active checks the production author path runs;
  • sweep_folder reads the folder's current children instead of taking a
    hand-built vec; read_key_of factored onto read_key_under(seed, node).

Skipped, with reasons: folding read() and version_block() onto one
read-through helper — the two differ in plaintext ownership (read hands the
cache slice straight to take_from; version_block owns a Zeroizing block it
clamps into), so a shared owned-return helper adds a full-block copy to read's
hot path and a borrowed return would drop the terminal-owner zeroize on the miss
path. Moving rotate_read_epoch into crates/engine/src/testkit/owner_root.rs
as a resealed_owner_root sibling is the right home but is outside this PR's
file ownership — worth a follow-up.

/security-review — run on the same diff. No findings. What was traced:
StreamHandles are monotonic and never recycled, so two handles on one file get
disjoint cache namespaces and a cross-handle serve is structurally impossible;
base_len is monotonically non-increasing and clamp_into runs on both the hit
and the miss path; read_pinned_range hard-clamps the widened window to the
pinned version, so the extra bytes can never come from another version, node, or
account; the retained block moves into insert, which wraps in Zeroizing
before any early return, so even a refused block is wiped. The one design note it
raised — that the cache's own header stated its safety argument in terms of
version identity only — is folded in: the header now records that blocks are held
whole and clamped at use.

/crypto-privacy-reviewnot run.
The diff touches no key or seal material: crates/fuse/** moves
already-decrypted plaintext between the engine and the mount's existing
plaintext cache, and the crates/engine/tests/write_plane.rs change is
test-only, composing already-public engine and core primitives with the suite's
fixture constants.

Closes #1168
Closes #1218

Note

Fix fuse base block serving from chunk cache with hot/scan retention policy

  • Introduces a Retain enum (Hot/Scan) in ops.rs to distinguish cache-promoting reads (user reads) from non-promoting reads (write merges, commit walks).
  • Adds a peek method to ChunkCache in cache.rs that returns a block without updating LRU order, used by scan paths.
  • version_block now fetches full-sized blocks from the engine and clamps locally, fixing cases where base blocks were not served from cache correctly.
  • Drops empty blocks at cache insert time rather than at call sites, simplifying read in ops.rs.
  • Reworks the epoch-lag test in write_plane.rs to simulate a realistic read-epoch rotation with lazy sweep, replacing the previously vacuumed test.
  • Behavioral Change: commit walks and sub-block write merges no longer promote or populate cache entries; only explicit read paths retain hot blocks.

Macroscope summarized 94d9cc8.

Summary by CodeRabbit

  • Bug Fixes
    • Improved consistency when read-scope keys rotate, allowing unsynchronized folders to continue serving the last known-good content until refreshed.
    • Fixed stale data appearing after truncation followed by sparse writes.
    • Improved handling of pending writes and repeated reads to prevent unnecessary data fetching.
    • Preserved cached reader data when new versions are committed, improving read performance and continuity.

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5066627a-4d0a-4b98-952f-fb4ec2d32734

📥 Commits

Reviewing files that changed from the base of the PR and between abc9a8d and 94d9cc8.

📒 Files selected for processing (4)
  • crates/engine/tests/write_plane.rs
  • crates/fuse/src/cache.rs
  • crates/fuse/src/ops.rs
  • crates/fuse/tests/fuse_op_core.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

The PR adds genuine read-epoch rotation coverage for lagging folders and updates FUSE block caching to preserve hot blocks, avoid scan pollution, and apply per-handle truncation clamping.

Changes

FUSE block cache read semantics

Layer / File(s) Summary
Cache retention contract
crates/fuse/src/cache.rs
ChunkCache adds non-promoting peek access, rejects empty blocks, and documents handle-owned truncation floors. Tests cover empty-block rejection and eviction order.
Operation core block access
crates/fuse/src/ops.rs, crates/fuse/tests/fuse_op_core.rs
OperationCore separates hot reads from scan reads, caches full base blocks, clamps data at the pending length, and tests reuse across writes, reads, truncation, and commits.

Read-epoch rotation coverage

Layer / File(s) Summary
Rotation and lazy folder sweep
crates/engine/tests/write_plane.rs
The test harness authors rotated scope roots, derives keys under selected epochs, simulates root rotation, and verifies an unswept folder rejects stale content before a sweep exposes the updated child.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 94d9c

The change routes base-block reads through the cache and makes the epoch-lag test exercise its intended rejection path; no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

Suggested labels: release:cipherbox-fuse:fix

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes both primary changes: cached FUSE base-block reads and the repaired epoch-lag test.
Linked Issues check ✅ Passed The changes satisfy #1168 cache, truncation, scan-retention, and test objectives, and satisfy #1218 rotation, lagging-folder, and positive-control objectives.
Out of Scope Changes check ✅ Passed The changes are limited to the linked FUSE cache behavior and epoch-lag test objectives, with supporting tests and fixtures.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/version-block-chunk-cache-and-epoch-lagged-focus-test

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.

@FSM1
FSM1 force-pushed the fix/version-block-chunk-cache-and-epoch-lagged-focus-test branch from 675edf9 to 0951284 Compare August 19, 2026 14:14
…och-lag test

`OperationCore::version_block` read the base version straight off the engine
and never touched `ChunkCache`, so every sub-block write, every `read_pending`
window, and the whole commit walk re-fetched blocks the mount already held. It
now reads through the cache. Blocks are cached whole and the per-handle
truncate floor is clamped at use, because that floor belongs to the handle and
not to the stream every handle on the file shares. A `Retain` argument says
whether a path will come back to the bytes: the commit walk and the write-merge
read through without retaining or promoting, so a linear pass over a file
cannot spend the budget — or the recency order — on blocks it will never ask
for again.

`an_epoch_lagged_focus_folder_rejects_without_raising_abuse` asserted nothing.
Raising the read-epoch floor directly revoked the cached scope read seed, so
the focus-folder leg was skipped entirely and no rejection ever occurred. The
harness now drives a real read-plane rotation: the root republishes at the next
epoch under a fresh seed, which is what raises the floor and deposits the seed
the focus leg needs, with an interior folder still publishing below it. A
positive control in the same test re-seals those same children at the current
epoch and watches them render, so the rejection cannot be a leg that never ran.

Closes #1168
Closes #1218
@FSM1
FSM1 force-pushed the fix/version-block-chunk-cache-and-epoch-lagged-focus-test branch from 0951284 to 94d9cc8 Compare August 19, 2026 15:34
@FSM1
FSM1 marked this pull request as ready for review August 19, 2026 15:34
@FSM1

FSM1 commented Aug 19, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown

Greptile Summary

The PR routes FUSE base-version block reads through the existing chunk cache while preserving per-handle truncate clamping, and repairs an epoch-lag test so it exercises a real read-plane rotation.

  • Adds hot-versus-scan cache retention behavior and non-promoting cache peeks.
  • Centralizes rejection of empty cache entries and adds cache/read-path regression coverage.
  • Reauthors the epoch-lag fixture with a rotated scope root and a swept-folder positive control.

Confidence Score: 5/5

The PR appears safe to merge with no actionable correctness or security failures identified.

Whole cached blocks remain isolated by unique stream handles, are invalidated with stream release, and are clamped to each pending handle’s truncate floor on every use; the revised test fixture uses checked production authoring helpers and includes a positive control.

Important Files Changed

Filename Overview
crates/fuse/src/ops.rs Adds cache-aware base-block reads with explicit hot/scan retention, whole-block caching, and per-use truncate clamping.
crates/fuse/src/cache.rs Adds non-promoting cache lookup and centralizes empty-block admission rejection without changing the cache’s ownership model.
crates/fuse/tests/fuse_op_core.rs Adds regression coverage for cached write merging, pending reads, truncate floors, and scan-induced eviction behavior.
crates/engine/tests/write_plane.rs Reworks the epoch-lag test fixture to perform a real read-plane rotation and verifies recovery after sweeping the focused folder.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    V[version_block request] --> S{Spill contains block?}
    S -->|Yes| P[Return spill block]
    S -->|No| F{Inside base_len floor?}
    F -->|No| Z[Return zero-filled block]
    F -->|Yes| R{Retention mode}
    R -->|Hot| G[Promoting cache get]
    R -->|Scan| K[Non-promoting cache peek]
    G --> H{Cache hit?}
    K --> H
    H -->|Yes| C[Clamp cached whole block to base_len]
    H -->|No| E[Read whole block from pinned stream]
    E --> C
    C --> I{Hot miss?}
    I -->|Yes| A[Insert whole block into cache]
    I -->|No| O[Do not retain]
    A --> Q[Return clamped block]
    O --> Q
Loading

Reviews (1): Last reviewed commit: "fix: serve fuse base blocks from the chu..." | Re-trigger Greptile

@FSM1
FSM1 merged commit 5719f03 into main Aug 19, 2026
128 of 136 checks passed
@FSM1
FSM1 deleted the fix/version-block-chunk-cache-and-epoch-lagged-focus-test branch August 19, 2026 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant