# Dovecot writable virtual-Maildir contract Research for [Establish Dovecot's writable virtual-Maildir contract](https://git.s1q.dev/phg/fuse-mail-dir-by-tag/issues/2), performed 2026-07-23 against Dovecot 2.4.4 documentation, Dovecot `core` commit [`7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2`](https://github.com/dovecot/core/tree/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2), and upstream Linux/libfuse documentation. ## Decision The tag namespace should be presented to Dovecot as a separate, private Maildir namespace with `mailbox_list_layout = fs`. The FUSE mount contains only generated mailbox directories and immutable message bytes; Dovecot's persistent control files and rebuildable index files live in separate native, writable directories. The FUSE daemon implements the small Maildir mutation protocol Dovecot actually uses: flag-changing `rename(2)`, `new/` to `cur/` `rename(2)`, and expunge `unlink(2)`. It rejects message-content writes, append/copy into the namespace, and mailbox creation/deletion/rename. The difficult part is concurrency, not the basic syscalls. Every tag folder is a distinct Dovecot mailbox and therefore has its own `dovecot-uidlist` and lock, while several message projections share one Source Maildir message. The FUSE daemon must be the cross-folder serialization point. It must identify a message by its stable Maildir base name, interpret a rename as a flag *delta* from the old name to the requested new name, and merge that delta atomically into current Source Maildir state. A stale old projection name must still resolve by base identity. Returning `ENOENT` for a stale flag rename is insufficient because current Dovecot source treats that result as a no-op, silently losing the requested flag change. This cross-folder merge behavior is an inference from Dovecot's per-Maildir locking model and its rename error handling. [Dovecot Maildir locking](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#locking), [Dovecot flag-change implementation](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-sync-index.c#L101-L155) There is a second lock boundary at the Source Maildir. If Dovecot also exposes or mutates that mailbox directly, a daemon that renames or unlinks Source Maildir entries without participating in Dovecot's `dovecot-uidlist` dotlock is an uncoordinated MUA; Dovecot documents that this can temporarily lose messages and assign new UIDs. The implementation must therefore either (a) make the Source Maildir delivery-only and inaccessible for direct Dovecot mailbox operations, or (b) acquire the same Dovecot-compatible source uidlist lock for every source rename/unlink. Option (b) requires a per-instance source control-path setting whenever that control directory is not the Source Maildir itself. [Dovecot Maildir locking](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#locking), [current uidlist lock source](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-uidlist.c#L135-L218), [control-path-derived lock target](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-uidlist.c#L258-L293) ## 1. Namespace and on-disk shape Dovecot's default Maildir layout is Maildir++, where mailbox `foo.bar` is physically `.foo.bar/`. The desired direct tree, `//{cur,new,tmp}`, therefore requires `mailbox_list_layout = fs`; changing only the namespace separator does not change the storage layout. [Maildir directory layouts](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#directory-layout), [namespace separator versus layout separator](https://doc.dovecot.org/2.4.4/core/config/namespaces.html#hierarchy-separators) The mounted tree is: ```text / / cur/ new/ tmp/ / cur/ new/ tmp/ ``` All three subdirectories must exist before a tag folder becomes visible. Dovecot verifies or creates `cur`, `new`, and `tmp` when it opens a Maildir, so publishing an incomplete folder would make it attempt unsupported `mkdir(2)` operations. [Dovecot Maildir subdirectory creation](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-storage.c#L232-L255) The FUSE root is not an INBOX. It is the storage root for the generated children below a separately prefixed namespace. For a slash-separator deployment, the current Dovecot 2.4 form is: ```dovecot namespace tags { type = private separator = / prefix = 00_tags/ inbox = no list = yes subscriptions = no mail_driver = maildir mail_path = /run/fuse-mail-dir-by-tag/personal mailbox_list_layout = fs mail_control_path = /var/lib/dovecot/tag-view/personal/%{user}/control mail_index_path = /var/lib/dovecot/tag-view/personal/%{user}/index maildir_very_dirty_syncs = no mailbox_list_index = no maildir_copy_with_hardlinks = no } ``` For a dot-separator deployment, only the visible namespace syntax changes: ```dovecot namespace tags { separator = . prefix = 00_tags. # all mail_* and mailbox_* settings are identical } ``` The prefix must end in the configured separator. All visible namespaces in one Dovecot configuration must use the same separator; `.` and `/` are both supported, and the separator cannot occur literally in an individual visible mailbox component. [Dovecot namespace settings](https://doc.dovecot.org/2.4.4/core/config/namespaces.html#settings), [Dovecot hierarchy-separator constraints](https://doc.dovecot.org/2.4.4/core/config/namespaces.html#hierarchy-separators) Consequently, the service's required Mailbox hierarchy separator option must match Dovecot's visible separator, and its reversible tag encoder must encode at least that character, `/`, the encoder's own escape character, unsafe leading `~`, NUL, and any name that would be `.` or `..`. Dovecot itself offers `` storage escaping for layout-invalid characters, but the project needs stable encoded folder names before Dovecot sees them, so the daemon's encoder remains the canonical mapping. [Dovecot storage-name escaping and filesystem-name validation](https://doc.dovecot.org/2.4.4/core/config/mail_location.html#mailbox-list-storage-escape-char) `maildir_very_dirty_syncs` must remain disabled because enabling it assumes Dovecot is the only process changing `cur/` and can cause external changes to be missed. The conservative baseline also disables `mailbox_list_index`, making mailbox discovery scan the FUSE tree instead of trusting a separately persisted list cache; this trades LIST/STATUS performance for correct discovery of generated folders. [Dovecot `maildir_very_dirty_syncs`](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#maildir-very-dirty-syncs), [Dovecot mailbox-list index behavior](https://doc.dovecot.org/2.4.4/core/summaries/settings.html#mailbox-list-index) Disabling `mailbox_list_index` also means the IMAP NOTIFY extension is unavailable for this namespace; ordinary mailbox selection, polling, and IDLE remain the expected client observation paths. This is an explicit correctness-over-NOTIFY tradeoff because Dovecot states that externally delivered Maildir changes outside INBOX are not noticed with the list index enabled. [Dovecot mailbox-list index and NOTIFY](https://doc.dovecot.org/2.4.4/core/summaries/settings.html#mailbox-list-index), [Dovecot external-change warning](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#maildir-very-dirty-syncs) For Dovecot 2.3, the equivalent storage settings use the legacy location string, for example `location = maildir:/run/fuse-mail-dir-by-tag/personal:LAYOUT=fs:CONTROL=/var/lib/dovecot/tag-view/personal/%u/control:INDEX=/var/lib/dovecot/tag-view/personal/%u/index`; the namespace's `separator`, `prefix`, `inbox`, `list`, and `subscriptions` settings are unchanged. User documentation should provide version-matched examples rather than mixing 2.3 and 2.4 syntax. [Dovecot 2.3 Maildir configuration](https://doc.dovecot.org/2.3/configuration_manual/mail_location/Maildir/), [Dovecot 2.3 namespaces](https://doc.dovecot.org/2.3/configuration_manual/namespace/) ## 2. Control files, indexes, UIDs, and timestamps Each generated tag folder is an independent IMAP mailbox. Dovecot assigns its own UID sequence and UIDVALIDITY to that folder through `dovecot-uidlist`; the same Source Maildir message is allowed to have a different UID in another tag folder. Stability is required within each folder, not equality across folders. `dovecot-uidlist` also contains the mailbox GUID and next UID. [Dovecot Maildir UID mapping](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#imap-uid-mapping) `mail_control_path` must point outside the generated mount to persistent native storage. It holds at least `dovecot-uidlist` and `dovecot-keywords`; deleting it changes message UIDs and may lose keyword mappings. Dovecot requires these control files to be writable and updates the uidlist through append and temporary-file-plus-rename operations. [Dovecot Maildir control files](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#control-files), [current uidlist rewrite source](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-uidlist.c#L1404-L1455) `mail_index_path` must likewise point outside FUSE to native writable storage. Indexes are rebuildable, unlike control files, but Dovecot normally writes them alongside messages unless configured otherwise. Control and index roots must be unique to this namespace/instance (and to the user where more than one user can access an instance), because identically named mailboxes in two namespaces otherwise collide. [Dovecot index placement](https://doc.dovecot.org/2.4.4/core/config/mail_location.html#index-files), [Dovecot multiple-namespace control/index warning](https://doc.dovecot.org/2.4.4/core/config/shared_mailboxes.html#read-only-mailboxes) The projection filename must retain the Source Maildir's stable base name, defined as everything before the Maildir `:2,` information suffix. Dovecot deliberately compares and hashes that base independently of flags, while `dovecot-uidlist` may retain an old full filename after a flag rename. Reusing a previously expunged base name can be interpreted as restoring an old message, so the daemon must never synthesize or recycle base identities. [Dovecot base-name comparison](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-filename.c#L61-L92), [uidlist filename and unexpunge behavior](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#imap-uid-mapping) Message bytes and message `mtime` must not change. Dovecot opens Maildir messages read-only, treats delivered messages as immutable, and uses message `mtime` as IMAP INTERNALDATE. The projection should return the Source Maildir message size and timestamps and should include correct `S=` and, where known, `W=` filename fields so Dovecot has no reason to repair them by rename. [Dovecot read-only open path](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-mail.c#L24-L43), [Maildir filename size extensions and timestamp use](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#maildir-filename-extensions) By contrast, the reported `mtime` of affected `cur/` and `new/` directories must advance whenever their visible membership or names change, because Dovecot uses those directory mtimes to decide when to rescan. The tag root's `mtime` must likewise advance when tag folders appear or disappear. [Dovecot Maildir directory timestamp use](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#usage-of-timestamps) ## 3. Writable filesystem operation contract ### Reads Implement lookup/getattr, open/read/release, opendir/readdir/releasedir, and access checks with normal POSIX behavior. Message opens allow `O_RDONLY` only. Message write, truncate, chmod/chown, xattr mutation, and timestamp mutation are rejected, because Dovecot requires delivered message content to remain immutable. [Dovecot Maildir immutability](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#imap-uid-mapping), [Dovecot message open implementation](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-mail.c#L24-L43) ### Source Maildir locking Before a Source Maildir flag rename, `new/` to `cur/` transition, or unlink, acquire both the daemon's per-message lock and—when the source is directly accessible through Dovecot—the Dovecot-compatible lock associated with the source mailbox's `dovecot-uidlist`. Dovecot creates this lock through its dotlock implementation, refreshes the uidlist after lock acquisition, and keeps lock configuration such as exclusive creation and stale timeout with the uidlist object. Merely creating an arbitrary mutex or a differently located `.lock` file does not coordinate with Dovecot. [Dovecot uidlist lock implementation](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-uidlist.c#L135-L218), [Dovecot uidlist lock settings](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-uidlist.c#L258-L293) The specification must choose and document one supported source mode per instance: `delivery-only`, which forbids direct Dovecot access to the Source Maildir and needs no Dovecot source lock, or `dovecot-shared`, which requires the exact source control path and compatible dotlock behavior. Postfix/Dovecot delivery into `new/` does not itself require this lock; Dovecot explicitly distinguishes delivery from flag/expunge modification. [Dovecot Maildir locking and delivery](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#locking) ### Standard flags Dovecot represents the standard mutable IMAP flags in the `:2,` filename suffix: `D` = `\Draft`, `F` = `\Flagged`, `R` = `\Answered`, `S` = `\Seen`, and `T` = `\Deleted`; it sorts known flags when producing a new name. [Dovecot flag parser/writer](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-filename-flags.c#L11-L59), [Dovecot flag writer](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-filename-flags.c#L91-L150) Allow a rename only when source and destination are in the same generated tag folder and have the same stable base identity, and the operation is one of: 1. `cur/:2,` to `cur/:2,`; 2. `new/` (or `new/:2,`) to `cur/:2,`; 3. a size-field correction that leaves base identity and content unchanged, if the daemon did not already publish correct `S=`/`W=` values. Dovecot performs standard flag changes with a same-directory rename and treats `ENOENT` as no change; it moves messages from `new/` to `cur/` with rename and also treats a missing old name as evidence that another actor moved it. [Dovecot flag rename](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-sync-index.c#L101-L155), [Dovecot `new` to `cur` transition](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-sync.c#L470-L520) For a flag rename, compute `added = requested_flags - old_path_flags` and `removed = old_path_flags - requested_flags`, then under a per-Source-message lock apply `(current_flags union added) - removed` to the authoritative Source Maildir filename. This preserves unrelated concurrent changes made through another tag folder. If two operations conflict on the same flag, lock acquisition order determines the final state. Before replying success, atomically update the daemon's snapshot for every projection; after replying and releasing operation locks, invalidate every alias's old/new dentry. This is a project contract inferred from the cited Dovecot behavior; Dovecot does not coordinate locks between different Maildir mailboxes. Lowercase `a` through `z` are per-mailbox keyword slots mapped by each folder's `dovecot-keywords`, so they cannot safely become global state merely by sharing filename characters across tag folders. The first version should reject renames that add or remove non-standard keyword letters (or explicitly document keywords as unsupported), while preserving unknown pre-existing suffix characters. [Dovecot Maildir keyword mapping](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#imap-keywords) ### `new/` and `cur/` The FUSE view mirrors the Source Maildir's global location: a source message in `new/` appears in `new/` of all its tag folders; a successful Dovecot `new/` to `cur/` rename moves the Source Maildir message and all projections. The operation must be idempotent by stable base identity so a second tag folder acting on a stale `new/` name succeeds when the same message is already in `cur/`. Dovecot's `maildir_empty_new` controls whether it always performs this transition, while normal `\Seen` state remains the filename `S` flag. [Dovecot `maildir_empty_new`](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/maildir.html#maildir-empty-new), [Dovecot `new` scan/move source](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-sync.c#L470-L525) ### Deletion and expunge Setting `\Deleted` changes only the `T` filename flag. Actual expunge is `unlink(2)`; Dovecot treats an already missing path as already handled. The FUSE `unlink` implementation must resolve the stable base identity, atomically delete the authoritative Source Maildir message, remove all projections, and invalidate their dentries. This implements the agreed global-delete semantics. [Dovecot flag mapping](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-filename-flags.c#L24-L40), [Dovecot expunge implementation](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-sync-index.c#L81-L99) ### Explicitly rejected operations Reject `create`, `mknod`, message `write`, `link`, `symlink`, cross-mailbox `rename`, `mkdir`, `rmdir`, and rename of tag folders. This prevents APPEND/COPY/MOVE into a tag folder and manual folder mutation. A MOVE from a tag folder to a real mailbox remains possible at the Dovecot level as copy-out followed by expunge: Dovecot can fall back from hard linking to byte copying when linking is inaccessible or crosses filesystems, then the FUSE side sees only the final global unlink. [Dovecot Maildir copy fallback](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-copy.c#L23-L55), [Dovecot generic copy fallback](https://github.com/dovecot/core/blob/7bbc8f0d4687a9ce12354c8f8c939e21b52b20c2/src/lib-storage/index/maildir/maildir-copy.c#L126-L148) ## 4. FUSE cache/coherency contract The kernel caches name lookups, negative lookups, attributes, and file pages independently. libfuse exposes separate `entry_timeout`, `negative_timeout`, and `attr_timeout` controls; `kernel_cache` keeps file contents across opens and is documented as unsafe when contents change externally. [libfuse cache configuration](https://libfuse.github.io/doxygen/structfuse__config.html) Use the following safe baseline: - `negative_timeout = 0`, so a newly generated tag/message is not hidden behind a cached miss; - short, bounded entry and attribute timeouts (at most the promised near-real-time convergence interval) as a fallback; - no cached readdir results; - no FUSE writeback cache, because Source Maildir changes also occur outside the mount and writeback mode assumes all changes pass through FUSE; - do not enable unconditional `kernel_cache`; ordinary cached reads are acceptable per open, but the daemon must not promise that a pathname always refers to externally immutable data unless replacement/reuse is also prohibited. The timeout and writeback recommendations follow the documented cache semantics. [libfuse lookup/attribute/page-cache options](https://libfuse.github.io/doxygen/structfuse__config.html), [Linux FUSE write-through and writeback modes](https://docs.kernel.org/filesystems/fuse/fuse-io.html) Watcher-driven Source Maildir changes and every successful FUSE mutation must actively invalidate affected kernel state. Use `fuse_lowlevel_notify_inval_entry` for old and new names and parent attributes, `fuse_lowlevel_notify_delete` for removals where the cached child inode is known, and `fuse_lowlevel_notify_inval_inode` when cached attributes or data for a retained inode changed. For a FUSE-request mutation, enqueue alias notifications and send them only after replying to the triggering request and releasing operation locks. libfuse requires notifications to run outside related filesystem-operation execution paths and outside locks those operations need to avoid deadlock. [libfuse invalidation and delete notifications](https://libfuse.github.io/doxygen/fuse__lowlevel_8h.html#gab3e4b2d2631ff0e286e0287ac8b73320), [libfuse notification API details](https://libfuse.github.io/doxygen/fuse__lowlevel_8h.html) The daemon must invalidate all aliases of a Source message, not just the tag folder that triggered the mutation, and must update `cur/`, `new/`, tag-folder, and namespace-root attributes as applicable. If notification support is unavailable or an invalidation fails, the bounded cache timeouts are the correctness fallback. This fan-out rule is an inference required by the project's shared-state semantics and libfuse's per-dentry caching model. Dovecot documents FUSE dentry/attribute caching as problematic when multiple independent clients access shared storage and describes its NFS cache-flush settings as imperfect workarounds. This project instead has one local FUSE authority with explicit kernel invalidation and keeps indexes/control files off FUSE; therefore `mail_nfs_storage` and `mail_nfs_index` should remain `no` in the supported single-server topology. Multi-server access to one mounted instance is unsupported. [Dovecot filesystem/FUSE warning](https://doc.dovecot.org/2.4.4/core/config/mailbox_formats/overview.html#fuse-glusterfs), [current Dovecot NFS single-backend contract](https://doc.dovecot.org/2.4.4/core/config/nfs.html) ## 5. Permissions and service lifecycle Prefer mounting the filesystem as the same single mail UID/GID that Dovecot uses for the Source Maildir. A normal user FUSE mount is inaccessible to other users by default, including root; if the daemon and Dovecot must use different UIDs, mount with `allow_other` and `default_permissions`, expose restrictive ownership/modes, and enable `user_allow_other` in `/etc/fuse.conf`. libfuse warns that `allow_other` without `default_permissions` can reuse a cached permission decision across users. [libfuse security implications](https://github.com/libfuse/libfuse#security-implications) The Dovecot mail worker needs search/read permission for the root and generated directories and rename/unlink permission for projected message entries; the daemon itself needs read, rename, and unlink permission on the Source Maildir. The native control and index roots must be writable by the Dovecot mail UID/GID. Dovecot derives creation permissions from mailbox directories, so the projected directory ownership/modes must be internally consistent even though append and folder creation are rejected. [Dovecot filesystem permissions](https://doc.dovecot.org/2.3/admin_manual/filesystem_permission/), [Dovecot index-directory write requirement](https://doc.dovecot.org/2.4.4/core/config/mail_location.html#index-files) The unmounted mountpoint must not be writable by Dovecot. Otherwise a failed/unmounted service can cause Dovecot to create an empty replacement mailbox tree on the underlying directory; Dovecot gives the same safety requirement for remote mounts. The NixOS unit and documented Dovecot override must order the FUSE instance before Dovecot access, and a failed initial scan/mount must fail closed rather than expose an ordinary writable directory. [Dovecot unmounted-storage safety rule](https://doc.dovecot.org/2.4.4/core/config/nfs.html) The service should report mount readiness only after the initial header scan and complete projection snapshot are ready. Later reconciliation publishes a complete new snapshot, updates directory mtimes, and issues invalidations before it reports convergence. This is the previously agreed project consistency requirement, not a behavior supplied by Dovecot. ## 6. Required acceptance tests An implementation is compatible with this contract only if integration tests using a real supported Dovecot version demonstrate all of the following: 1. Both separator configurations list the same flat tag folders under `00_tags/` or `00_tags.` without changing the physical `fs` tree. 2. Each folder's UIDVALIDITY and existing message UIDs survive daemon restart, unmount/remount, Dovecot restart, a standard-flag rename, and a `new/` to `cur/` transition. 3. Reading/marking `\Seen`, toggling `\Flagged`, `\Answered`, `\Draft`, and `\Deleted` through one tag folder updates the Source Maildir and becomes visible in every projection. 4. Concurrent changes through two tag folders preserve unrelated flag deltas; a deliberately stale old pathname does not lose the requested change. 5. EXPUNGE in one tag folder removes the Source Maildir message and every projection while leaving each folder's surviving UIDs stable. 6. A Source Maildir delivery, external flag rename, and external unlink become visible within the documented convergence bound without remounting. 7. APPEND/COPY/MOVE into a tag folder, message-content modification, and tag-folder create/delete/rename are rejected, while MOVE from a tag folder to a real mailbox copies out and then globally expunges. 8. No Dovecot control, index, lock, subscription, or ACL file is created inside the FUSE mount. 9. Dovecot cannot create data below the bare mountpoint while the FUSE service is stopped or failed. 10. Permission tests cover the normal same-UID mount and, if supported, the `allow_other,default_permissions` deployment. 11. In `dovecot-shared` source mode, simultaneous direct Source Maildir and tag-namespace mutations coordinate through the source uidlist lock without UID churn, lost flags, or transient disappearance; in `delivery-only` mode, direct source access is demonstrably unavailable. ## Consequences for the implementation specification - Use a FUSE API capable of low-level inode/dentry invalidation notifications, not only timeout-based high-level callbacks. - Maintain a stable Source Maildir message-identity index and reverse index of all Message projection dentries so one mutation can update and invalidate every alias. - Serialize writes per Source message and merge flag deltas rather than replacing whole suffixes. - Make Source Maildir access mode explicit; supporting direct Dovecot access adds a required source-control-path option and Dovecot-compatible uidlist dotlocking. - Treat Dovecot control/index paths as persistent deployment state and include them in backup guidance; the FUSE projection itself remains reconstructible. - Version the tag-name encoding and treat changes to its separator or encoding as a mailbox migration, because changing folder storage paths disconnects them from their existing per-folder control state. - State explicitly that non-standard IMAP keywords and multi-server access are unsupported in the first version.