SQLite Snapshots
Graft tracks SQLite database paths as snapshots. A snapshot is an immutable view of a database state that can be recorded in a repository commit.
That makes a SQLite database path a first-class versioned object in Graft. The
repository does not have to treat data.sqlite as an opaque binary blob: it can
record a snapshot, compare supported row and schema changes, merge compatible
edits, and materialize the result back to a normal SQLite file.
Normal applications still see SQLite files:
project/ data.sqlite .graft/data.sqlite is the worktree path. The commit stores a snapshot descriptor
backed by Graft storage under .graft/.
Physical Worktree Mode
Section titled “Physical Worktree Mode”The default mode uses an ordinary SQLite file:
graft sql --db data.sqlite "INSERT INTO notes(body) VALUES ('hello');"The SQL transaction changes data.sqlite and possibly its WAL. It does not
write Graft storage directly. Staging is the storage boundary:
graft add data.sqlitegraft commit -m "add note"graft add uses SQLite’s online backup API to capture one committed database
state. It includes committed WAL frames without checkpointing or modifying the
source database, normalizes the private snapshot into a standalone rollback-
journal file, and compares its image with the staged or committed base in 4 KiB
Graft storage chunks. Only changed chunks are appended to Graft storage. The
physical database can use a larger legal SQLite page size, such as 8 KiB; storage
deduplication remains 4 KiB. If nothing changed, the existing CommitFileState
and snapshot are reused.
Rollback-journal databases follow the same path; there simply may be no WAL to include. Uncommitted transactions are never staged.
Materialized Files
Section titled “Materialized Files”By default, Graft materializes committed SQLite snapshots back to normal database files in the worktree after commit, checkout, switch, pull, and merge-continue.
That lets standard SQLite tools inspect the current state:
sqlite3 data.sqlite "SELECT count(*) FROM notes;"The repository commit still stores a verifiable snapshot descriptor, not a plain copy of the whole file.
Exporting
Section titled “Exporting”Use graft export when you want a separate normal SQLite file:
graft export --source HEAD --output data.head.sqlite data.sqliteExport does not move HEAD, change the index, or connect the exported file to
the current Graft volume.
External Edits
Section titled “External Edits”If a standard SQLite tool edits a tracked materialized database directly, commit the transaction and stage the path:
sqlite3 data.sqlite "INSERT INTO notes(body) VALUES ('external edit');"graft add data.sqliteManual WAL checkpointing is not required. Before checkout, switch, restore, or another operation that replaces the worktree file, close long-lived database connections. Graft obtains an exclusive SQLite lock, folds a WAL into the old main file, removes sidecars, and refuses replacement while another writer is active.
Optional Live VFS Mode
Section titled “Optional Live VFS Mode”The SQLite extension can open a path with vfs=graft when an application wants
SQLite commits to advance a live Graft Volume directly. This is an advanced
data-plane mode, not the repository control plane. Repository status, staging,
history, merge, and sync still use the CLI. Unlike physical staging, the live
Graft VFS write path requires SQLite page_size=4096 because each SQLite page
maps directly to one Graft page.
Do not edit the same logical database simultaneously through the Graft VFS and the platform’s normal file VFS. Choose one write path and close live connections before branch operations that replace or rebind database state.