Architecture Guide
This guide answers one question: after an application commits a SQLite transaction, how does that change become Graft history that can be compared, merged, synchronized, and restored?
It follows one repository containing app.sqlite, settings.json, and a large
attachments/report.pdf. These contents take different storage paths and meet in one repository
commit.
Graft maintains two histories
Section titled “Graft maintains two histories”This is the most important fact in the book.
Application history (repository history)branch/ref -> repository commit -> tree -> path blob | +-- file bytes / external pointer +-- SQLite snapshot descriptor
SQLite storage historysnapshot descriptor -> VolumeId + ordered log ranges | +-- (LogId, LSN) storage commits | +-- changed 4 KiB pagesA repository commit says which paths make up an application version. A storage commit says which 4 KiB pages changed from a prior SQLite state. They are not the same commit and are not one-to-one:
- one repository commit can reference several databases and ordinary files;
graft add app.sqlitecan create a storage commit before a repository commit exists;- a no-op add can reuse a snapshot without creating a storage commit;
- a branch ref points to a repository commit, never directly to an LSN.
The five states of one change
Section titled “The five states of one change”For an insert into a notes table:
1. worktree ordinary SQLite changes app.sqlite and perhaps app.sqlite-wal | | graft add v2. consistent private image (temporary) SQLite online backup captures committed transactions | | 4 KiB chunk comparison v3. storage snapshot changed pages enter an immutable segment and storage commit | | snapshot descriptor v4. index stage 0 for app.sqlite names that exact snapshot | | graft commit v5. repository history write blob -> tree -> commit, then move refs/heads/mainThe private image is only a capture boundary. The index is the authority for the next commit.
commit does not reopen a database that may have changed since add.
Four questions for every operation
Section titled “Four questions for every operation”- Which state does it read? Worktree, index, a commit, or a side of an active merge?
- Which state does it write? Storage, objects, index, ref, merge journal, remote, or worktree?
- What identifies the result? Byte hash, object ID, snapshot descriptor, or
(LogId, LSN)? - What remains authoritative after failure? A ref, index, merge journal, or rebuildable cache?
Read this section after completing a CLI or SDK quickstart. It explains the system boundaries needed to design a safe integration; task pages remain the source for command sequences, and the specifications define exact contracts.
Reading order
Section titled “Reading order”- Repository And Objects
- SQLite Snapshots
- Stage And Commit
- Checkout And Restore
- Diff And Merge
- Remotes And Recovery
- Hands-On Lab
Contract versus implementation
Section titled “Contract versus implementation”Path identity, object/ref/index semantics, snapshot content, merge results, and remote publication order are observable contracts. Fjall key encodings, cache names, Rust module boundaries, and temp file names are implementation details. They are useful for debugging but not product integration.
There is no supported repository-control compatibility layer based on SQLite PRAGMAs. Supported
integration surfaces are the graft CLI and its JSON output, the Node.js SDK, and the HTTP
remote packages.