entropyfs 0.2.0

Entropy-native Linux filesystem: persist irreducible state, materialize structure, preserve exact bytes.
# ADR-0019: FUSE kernel-cache invalidation from a dedicated notifier thread

**Status:** accepted · **Date:** 2026-08-25

## Context

Phase 3 live testing (git clone on a real mount) exposed two related
kernel-cache failures:

1. After `unlink`/`rmdir`, the kernel dcache and the per-directory readdir
   page cache kept stale entries: `ls -la` showed ghost `config.lock`
   entries (`-?????????`), and `rmdir` returned `ENOTEMPTY` because
   `shrink_dcache_parent` refused to prune the busy stale dentry — even
   though the store was correct and `lookup` already returned `ENOENT`.
2. Sending `FUSE_NOTIFY_INVAL_ENTRY` synchronously from inside the
   `unlink` request handler **deadlocked the entire session**: the kernel's
   `fuse_reverse_inval_entry` path blocks on locks held by the in-flight
   `unlink` request (observed in-kernel as the daemon thread wedged in
   `fuse_reverse_inval_entry`, with every subsequent request queued
   behind it).

The kernel's view of a directory (dcache + readdir page cache) is
independent of the store. `ReplyDirectory::add` (plain readdir) carries no
per-entry TTL, so the kernel caches dirents until invalidated. Without
explicit invalidation, removed names linger.

## Decision

- **All kernel notifications are queued to a dedicated `entropyfs-notify`
  thread** (`mpsc::sync_channel`, capacity 4096) that performs the actual
  `Notifier` calls. No notification is ever sent from inside a FUSE
  request handler — the kernel notify path can block on locks held by the
  in-flight request.
- On every directory mutation the parent's cached dirents + attrs are
  invalidated (`inval_inode(parent, 0, 0)`), and for removed names the
  stale dentry + child inode are dropped via `FUSE_NOTIFY_DELETE`
  (kernel >= 4.18; drops the dentry, invalidates the child, signals
  inotify), falling back to `inval_entry` + `inval_inode` on kernels
  without it.
- The queue is **bounded and best-effort**: a dropped invalidation
  (`try_send` full/disconnected) only delays cache freshness until an
  entry/attr TTL expires (1 s in v1). Invalidation can never corrupt data
  (§24); it is a performance/observability optimization.
- The notifier slot is seeded by the mount after `spawn_mount`
  (`BackgroundSession::notifier()`); unit tests and pre-mount operation
  simply no-op.

## Consequences

- Mutating handlers (`create`, `mknod`, `mkdir`, `symlink`, `link`,
  `unlink`, `rmdir`, `rename`) queue invalidations after their store
  transaction commits; `rename` invalidates both the source name and the
  destination name (and the replaced destination, if any).
- The notifier thread lives for the lifetime of the filesystem instance
  and exits when the sender is dropped.
- Phase 6 may tune TTLs or switch to readdirplus; the invalidation
  discipline remains.