Skip to main content

Persistence

The mailbox is files in a git repository you own. Three crates make that usable as an app store: git talks to the workdir, crypto seals documents at rest, and db queries structured records as GraphQL.

This page is those mechanics. Recipient encryption (Signal, ML-KEM, PQXDH) stays on Cryptography. The messaging record shapes — profile, contacts, outbound, inbox — stay on Data structures.

It is research and development — subject to change. These crates are closed-source. Galleries are the public surface.

git​

git-core is a GitRepo trait with two backends. Desktop uses gitoxide (gix) on the native filesystem. The browser uses isomorphic-git and origin-private storage, reached from Rust through wasm-bindgen.

gitoxide does not yet run as a whole on wasm32-unknown-unknown (index, refs, config, tempfile, transport), and it memory-maps packfiles — OPFS has no mmap. The trait keeps stories platform-agnostic until that lands upstream.

Open the git gallery

StoryWhat it exercisesLive
InitGitRepo::init in a throwaway workdirgui/git/init
Fileswrite / read / edit / remove / listgui/git/files
Statusmutations then GitRepo::statusgui/git/status
Cloneauthenticated clone, commit, branches, log, diff, fetch, pull, pushgui/git/clone
Storagefiles as a storegui/git/storage
Syncremotes staying in stepgui/git/sync

Clone on the web needs a CORS proxy (the gallery default is a public demo proxy). Connection fields persist in localStorage. Desktop skips the proxy.

GitHub, GitLab, and Codeberg may treat this mailbox use as against their terms. They may throttle, block, or close the repo. A first-party git server as part of the service is a later possibility, not something to wait on. Background: this thread. How to start on a host you already have: Using GitHub, GitLab, and Codeberg.

Try path: Your storage.

crypto​

crypto-core is the utility crate the store imports: hashing, AES-256-GCM, RSA-OAEP-4096, Argon2id password envelopes, a CSPRNG, and a cascade manager. It is a Rust-native primitive set. It is not interoperable with the older JS cryptography app (different KDF, envelope, and hashing conventions).

AreaAlgorithm
Randomgetrandom (Web Crypto in WASM)
HashingSHA-256, SHA-512, SHA3-512 over raw bytes
SymmetricAES-256-GCM (12-byte nonce, 16-byte tag)
AsymmetricRSA-OAEP-4096 with SHA-256 (SPKI / PKCS#8 DER)
PasswordArgon2id (19 MiB, t=2, p=1) → AES-256-GCM, versioned envelope
CascadeOrdered layers. AES and password live in core; Signal, PQXDH, and ML-KEM appear in the gallery.

Product at-rest sealing is Argon2id then AES-GCM on flush. That job is separate from the recipient cascade. Architecture draws the split.

The gallery’s multi-protocol story can stack Signal, PQXDH, and ML-KEM so you can see layers. The product recipient path does not add a password layer.

Open the crypto gallery

StoryLive
Randomgui/primitives/random
Hashinggui/primitives/hashing
AES-GCMgui/primitives/aes-gcm
RSA-OAEPgui/primitives/rsa-oaep
Password encryptiongui/primitives/password-encryption
ML-KEM-1024gui/primitives/ml-kem-1024
Signalgui/primitives/signal
PQXDHgui/primitives/pqxdh
Multi-protocol cascadegui/cascade/multi-protocol

Try path: Encryption tools.

db​

db-core is a small GraphQL engine over a StorageBackend. Memory is the default. The git feature uses git-core. Types marked @encrypted (GraphQL SDL) or "encrypted": true (JSON schema) are password-wrapped through crypto-core on flush.

The developer API is GraphQL query, mutation, and polling subscription strings. There is no commit, push, or pull on that surface. The app loads a schema (Schema::from_json_str or Schema::from_sdl); git stores JSON row files plus schema.json and schemaVersion.json. An empty repo is bootstrapped from the app schema.

Layout is hybrid JSON: singletons as one file, collections as folders of row files, nested collections under parents. Conflicts are per document when two clients edit the same JSON row. Schema-stamp conflicts are blocking. Multiple remotes act as equal-peer backups: sync pushes the same snapshot to each reachable remote, seeds empty ones, and surfaces mismatches.

When the store is behind the app schema, Migration::from_diff plus Database::open_with_migrations rewrites files. from_diff matches types and fields by name. Drop ops delete data.

Open the db gallery

StoryLive
Chat databasegui/database/chat-database
Schema designergui/database/schema-designer
Migrationsgui/database/migrations

Record shapes for the messenger (v6) are on Data structures. Try path: Encrypted records.

Next: Network — schema-driven routes, then git as a WebRTC broker.