Implement rollback-safe tree mutations

This commit is contained in:
Hermes Agent
2026-08-09 22:58:37 +00:00
parent 834df46818
commit 410007c012
6 changed files with 1368 additions and 0 deletions

36
docs/tree-mutations.md Normal file
View File

@@ -0,0 +1,36 @@
# Tree mutations
`TreeMutator` owns the `pass rm`, `mv`, and `cp` filesystem semantics in
`crates/storage`. Callers provide typed command requests, a confirmation
decision, secret-key access when selective reencryption is needed, and a
`TreeCommitter`; they never inspect or mutate the password-store tree.
Sources use the repository resolver. A trailing slash explicitly selects a
directory, while a logical path occupied by both `name.gpg` and `name/` is
otherwise rejected as ambiguous. An existing destination directory is a
container, so the source basename is appended. A destination ending in a slash
must already be a directory. Same-object transfers, moves into their own
subtree, entry/directory type collisions, symlinks, and subtrees containing
unknown auxiliary files are rejected before any write.
Entry overwrites require either `--force` or an affirmative confirmation.
Directory removal requires the recursive flag. Whole-directory transfers copy
their `.gpg-id` and optional `.gpg-id.sig` boundaries; an existing destination
policy is rejected instead of partly merging policy trees. Entries whose
effective destination recipients differ are decrypted and reencrypted with the
embedded OpenPGP backend, while matching ciphertext is preserved byte for byte.
Every destination entry and policy is atomically and durably written before a
move removes its source. The operation retains source and overwritten
destination bytes until the commit callback succeeds. A filesystem or commit
failure restores both sides and removes directories created by the failed
transaction. Successful moves and removals then prune empty source directories.
The commit callback must report failure only when it has not created a commit;
it receives the compatible intent `Remove ... from store.`, `Rename ... to
....`, or `Copy ... to ....`.
Compatibility tests materialize the shared upstream-format fixtures and cover
entry and subtree mutations, recursive requirements, cancellation, forced
overwrite, directory destinations, ambiguity, nested signed recipient
boundaries, selective reencryption, source preservation, source cleanup, and
exact-tree rollback after a simulated commit failure.