TigerFS 0.7 turns the filesystem into a fully transactional workspace. Every write produces an entry in .log/, named savepoints survive across sessions in .savepoint/, and .undo/ reverses any operation, log entry, or savepoint with a single touch. Per-user filtering means multiple agents can collaborate on the same workspace without stepping on each other, and undo-of-undo closes the loop so changing your mind about a rollback is itself a one-touch action. The combined effect is that a coding agent can experiment freely against a database and the human can always trace back, attribute, or reverse exactly what changed.
This release also makes TigerFS noticeably more POSIX-faithful. Atomic-rename writers (Vim, Claude Write/Edit, any editor that swaps a tmp file into place) now work correctly; directory mtimes reflect real changes via a database-side trigger plus a new noac mount option; user-created dotfiles like .gitignore coexist with the .log/ and .savepoint/ control surface without collision. Schema modeling expanded too: tables with composite primary keys now mount cleanly (rows appear as region,user_id directories), the directory structure is parent-pointer-based so a rename is a single-row update, and SELECT-only database roles work end-to-end. Recursive scanners like rm -rf and find no longer infinite-loop in the virtual tree, and a longstanding source of EIO and ENOENT under load on Linux FUSE mounts is gone.
Upgrading from 0.6 requires tigerfs migrate. The migration adds parent_id, filetype, and filename columns to existing backing tables and rebuilds history triggers; it is idempotent and dry-run-safe. Undo of log entries created before the migration is blocked with EPERM because pre-migration history lacks the parent-pointer information that 0.7's undo needs to be correct; .log/ and .history/ remain readable for those older entries. Fresh 0.7 installs are unaffected.
Operation log, savepoints, and atomic undo
- Operation log (
.log/) -- every write produces a log entry capturing user, timestamp, before/after content, and operation type; browse via.log/.last/N/.export/jsonor index queries.log/.by/file_id/,.log/.by/user_id/,.log/.by/type/ - Diff symlinks -- each log entry exposes
before,current, andaftervirtual files sodiff -u .log/<id>/before .log/<id>/currentworks directly - Savepoints (
.savepoint/) -- named bookmarks created by writing JSON to.savepoint/<name>.json; survive across sessions and users - Auto-savepoints -- configurable per-mount inactivity gap (
--auto-savepoint-interval, default 30 minutes) records bookmarks automatically at session boundaries - Undo (
.undo/) -- three modes:.undo/id/<log_id>/.apply(single op),.undo/to-id/<log_id>/.apply(multi-file rollback to a log entry),.undo/to-savepoint/<name>/.apply(multi-file rollback to a savepoint); preview affected files via.undo/<mode>/<target>/.info/summarybefore applying - Per-user undo -- filter any undo by user via
.by/user_id/<user>/.apply; agents only roll back their own changes in collaborative workspaces - Undo of undo -- undo operations are themselves logged, so you can reverse an undo by undoing its log entry; directory renames round-trip correctly even when batched with child file renames
Schema model and migration
- Composite primary key support -- tables with multi-column PKs (e.g.,
PRIMARY KEY (region, user_id)) now mount cleanly; rows appear as comma-encoded directories likeus,1, with,and/URL-escaped in PK values - Relational directory structure -- file/directory rows now reference their parent by
id(UUID) rather than path string, so a directory rename is a single-row UPDATE that all children inherit (ADR-017) tigerfs migrateupdates -- new migrations move history triggers and add parent-pointer columns; idempotent and dry-run-safe
Filesystem behavior and correctness
- POSIX rename-as-replace -- the atomic-rename pattern used by Vim, Claude Write/Edit, and most editors works correctly:
rename(tmp, target)replacestargetatomically instead of erroring - Directory mtimes via trigger +
noacmount option -- directory mtimes reflect actual filesystem changes via a database-side trigger; the newnoacmount option keeps the kernel from caching stale attributes across mounts of the same database - Real dotfiles in file-first workspaces -- user-created dotfiles (
.gitignore,.editorconfig) coexist with the control-surface dotnames (.log/,.savepoint/,.undo/, etc.) without collision inmarkdown,history-built workspaces - Directory-listing recursion safety -- recursive scanners (
rm -rf,find, agents probing structure) no longer infinite-loop on capability dirs; pipeline depth is capped and self-referential capability dirs are suppressed past a configurable limit - FUSE_INTERRUPT decoupling -- kernel interrupt signals (from
SIGURG-driven Go preemption) no longer propagate ascontext.Canceledinto the DB layer, eliminating spuriousEIO/ENOENTunder load on Linux FUSE mounts
Workspace identity and connectivity
- Mount-level user identity via
.info/user-- the mount's identity (used for per-user filtering on.log/and.undo/.by/user_id/) is exposed as a virtual file; configure via--user,TIGERFS_USER, or the process owner's username - SELECT-only roles supported -- primary-key and unique-constraint discovery now reads
pg_constraintinstead of relations a read-only role can't access, so workspaces mounted with a SELECT-only DB user work end-to-end
Tools
tigerfs-stressharness -- new companion binary for soak-testing a workspace under deterministic, PRNG-seeded filesystem + undo workloads; ships with diagnostic dumps, end-of-run monotonicity reporting, and a Docker FUSE mode (./scripts/test-docker.sh)
Breaking Changes
- 0.6 -> 0.7 history format migration. Run
tigerfs migrateon workspaces upgraded from 0.6 -- it addsparent_id/filetype/filenamecolumns to backing tables and rebuilds history triggers. The migration is idempotent. Undo of log entries created before the migration is blocked (EPERM) because pre-migration history has lossyparent_idinformation;.log/and.history/remain readable for those entries. Fresh 0.7 installs are unaffected.