Skip to content

ADR-0002 — Storage backends

  • Status: Accepted (2026-07-24, Opus review)
  • Related: ROADMAP v0.4, CODE_REVIEW M2
  • Acceptance note: Confirmed. For v0.2 only GitBackend is implemented, but the Backend trait (with read_snapshot taking a SnapshotRef that carries an optional revision) is defined now so the --revision fix and later backends are additive. fs/rclone/restic land in v0.4.

The Charter names Git as the first storage backend, implying others. The user wants snapshots to go to “various places / NAS / cloud providers.” Today snapshot.rs calls the git executable directly throughout — repository init, staging, commit, push, force-push, delete, history — so storage location and the snapshot workflow are fused.

We need to separate what a snapshot is (a versioned, reviewable tree of manifest + files) from where it lives.

Define a Backend trait in rivet-core and move all git calls behind a GitBackend in rivet-backend. Add further backends as separate implementors.

pub trait Backend {
fn id(&self) -> &'static str; // "git", "fs", "rclone", "restic"
fn init(&self, cfg: &BackendConfig) -> Result<()>;
fn write_snapshot(&self, snap: &SnapshotTree, msg: &str) -> Result<SnapshotId>;
fn read_snapshot(&self, id: &SnapshotRef) -> Result<SnapshotTree>; // supports --revision
fn list(&self) -> Result<Vec<SnapshotMeta>>; // history
fn push(&self) -> Result<()>; // no-op for local fs
}

Backends shipped by v0.4:

  • GitBackend — the reviewable default; unchanged behaviour, keeps gh private-remote automation. SnapshotRef gains Git-ref support → fixes H3.
  • FsBackend — a plain local or already-mounted directory. This is the NAS answer when the share is mounted (SMB/NFS); no extra dependency.
  • RcloneBackend — delegates to rclone for the long tail of cloud/NAS: S3, Backblaze B2, Google Drive, Dropbox, SFTP, WebDAV, SMB. One dependency, ~70 providers, matches Rivet’s “delegate to proven tools” principle.
  • ResticBackend — deduplicated, encrypted, incremental history for users who want backup semantics without Git.

Config lives in a [backend] (and repeatable [[backend]]) block in rivet.toml; rivet init --backend <id> scaffolds it. Multiple destinations are allowed (fan-out). Non-Git backends get encryption at rest: restic is native; fs/rclone wrap the snapshot tree with age (ADR-0003 primitive) before it leaves the machine.

  • Git-only, tell users to git remote to a NAS. Doesn’t cover object stores or consumer clouds, and forces Git even where the user wants plain files. Rejected — the user explicitly asked for NAS/cloud choice.
  • Reimplement S3/SFTP clients in-tree. Large surface, security-sensitive, redundant with rclone/restic. Rejected.
  • Only restic. Great for backup, poor for the “reviewable in a Git diff” property that makes Rivet auditable. Keep Git as default, restic as an option.
  • Positive: storage is a user choice; NAS/cloud covered with minimal new code; --revision falls out of read_snapshot.
  • Positive: whole-snapshot encryption at rest is available independently of the per-secret vault.
  • Cost: an abstraction over heterogeneous history models (Git commits vs restic snapshots vs fs timestamps); SnapshotId/SnapshotRef must be general.
  • Risk: rclone/restic become optional runtime dependencies — handled the same way as snap/flatpak today (availability check → notice, never a hard failure of unrelated backends).