Data model and durability
Database
One SQLite file, default ${XDG_STATE_HOME:-$HOME/.local/state}/session-bus/bus.db, overridden with BUS_DB. Every command that opens it uses the same rule and creates the directory if needed.
Settings: journal_mode=WAL, synchronous=NORMAL, busy_timeout=5000, foreign_keys=ON. Writes run in BEGIN IMMEDIATE transactions.
| Table | Columns |
|---|---|
principals | id, created_at, disabled_at |
tokens | id, hash, prefix, scopes, creator_id, label, created_at, expires_at, revoked_at, last_used_at |
sessions | name, owner_principal, description, created_at, last_seen |
messages | id (autoincrement), to_session, from_session, from_principal, token_id, body, thread_id, created_at, acked_at |
meta | schema (1), bus_instance_id (a UUID) |
Times are milliseconds since the epoch. messages has an index on (to_session, id) for inbox reads.
Durability
- WAL with
synchronous=NORMALsurvives a process crash without losing committed transactions.scripts/e2e.shsends a message, killsservewithkill -9, restarts it and reads the message back. - A power loss or OS crash can lose the most recent transactions. Accepted.
- A clean shutdown (SIGTERM, SIGINT) checkpoints the WAL and closes the file.
- Pruning deletes only acked messages whose
acked_atis more than 7 days old. It never deletes unacked mail.
Lock files
serve, restore and the push shim share one lock routine.
- The lock file holds
{pid, boot_ms}. It is written to a temp file, fsynced, and hard-linked into place, so it appears atomically. - A lock is live if the pid exists (or signalling it gives
EPERM), the boot time matches within 60 s, and it is not our own pid. Anything else is stale and is reclaimed once. - Two processes reclaiming the same stale lock at the same moment can race. There is no
flock. This is a known shortcut.
| Lock | Taken by | Effect |
|---|---|---|
<BUS_DB>.lock | serve, before opening the DB or binding the port | a second serve exits 3 (serve already running (pid N)) |
<BUS_DB>.lock | restore, for its whole run | restore exits 3 while serve runs |
shim-<name>.lock in the state dir | the push shim | a second shim for the same name exits 3 |
Backup and restore rules
backup <path>runsVACUUM INTO <path>, which is safe whileserveruns. The copy is a single file (journal modeDELETE) and gets a freshbus_instance_id. The target must not exist.restore <backup>requiresserveto be stopped. It holds the lock, copies the backup tobus.db.tmp, checks it is a bus database, gives it a freshbus_instance_id, fsyncs it, deletes the old-waland-shmfiles, renames it into place and fsyncs the directory. If any step fails, the live database is untouched.- There is no
--force. - Because the instance id changes, push shims reset their cursor and push unacked mail again. Delivery stays at-least-once.
Shim state
The shim never opens SQLite. It writes shim-<name>.json (mode 0600) with {bus_instance_id, high_water_id}, via a temp file and rename. A missing or corrupt file means "start from 0".