Skip to content

Compatibility policy

What a Rivet release promises about data written by a different Rivet release, and which interfaces you can build against without expecting them to move.

This policy is in effect — it took hold at v1.0.0, and the current release is v1.0.1. It does not extend backwards: the v0.x series made no such promise and changed the snapshot format once along the way (v1 → v2, in v0.3).

You have You can Under v1.x
A snapshot written by v1.x Read and restore it with any later v1.y Guaranteed
A snapshot written by v1.x Read it with an earlier v1.w Only if it uses no newer optional fields
A rivet.toml from v1.x Use it with any later v1.y Guaranteed
A declarative plugin from v1.x Load it with any later v1.y Guaranteed
--json output Parse it in a script Guaranteed; fields may be added
The Rust library crates Call them from your own code No guarantee — see below

The single sentence version: a v1.x Rivet restores any snapshot any v1 Rivet ever wrote. Everything else on this page is detail about how that is achieved and where it stops.

Four on-disk interfaces are frozen at v1.0.

1. The snapshot manifest — manifest.toml

Section titled “1. The snapshot manifest — manifest.toml”

Carries format_version = 2. Version 2 is the v1.0 format, and it is the version a v1.x Rivet writes.

Version 1 manifests (pre-v0.3, Arch-only) are still migrated in memory on read, and the next snapshot rewrites the repository as version 2. That migration path stays for the whole v1.x line.

A snapshot repository contains rivet.toml, manifest.toml, files/ mirroring paths relative to the source user’s home directory, secrets/ for age ciphertext, and a generated README.md. Those names and that meaning are frozen. New sibling entries may be added; existing ones will not be renamed or repurposed.

The approval file: selected groups, package sources, [[backend]], [vault], [[secret]], [[track]], [profile.<name>].

It carries no version field, deliberately. Every field is #[serde(default)] and every default is the conservative direction — an unrecognised or absent table means not selected, never selected. Rivet therefore never broadens what it captures because it read an old config, which is the failure mode a version field would exist to prevent. New tables may be added; existing keys keep their names and meanings through v1.x.

A plugin TOML file declares id, title, description, roots, packages, and hooks. Unknown keys are warned about and ignored rather than rejected, so a plugin written for a later v1.y still loads on an earlier one — it simply does less.

Through the v1.x line, a change to a frozen format may only:

  • add a new optional field with a serde default,
  • add a new value to an existing list, or
  • add a new variant to an enumerated field (os_family, distro_family).

It may not rename a field, change a field’s type, change what an existing value means, or make a previously optional field required. Any of those is a 2.0 change, and would come with both a format_version bump and a migration.

The practical consequence of “additive only” is the one asymmetry in the table at the top: forward compatibility is degraded, not broken. v1.0 reading a snapshot written by v1.5 parses successfully and ignores anything v1.5 added. That is usually right, but it means the older Rivet may restore less than the snapshot contains. When it matters, restore with a Rivet at least as new as the one that captured.

The third bullet is worth stating explicitly, because it is the case that would otherwise turn “degraded” into “broken”.

machine.os_family and machine.distro_family are enumerated. A snapshot from a future Rivet that supports a platform this one has never heard of — os_family = "windows", say — reads that field as unknown and carries on. It does not fail to load.

That behaviour is a promise, not an implementation detail. Rivet’s own capture never writes unknown for a host it successfully identified, so the value means exactly one thing on read: this snapshot came from somewhere this binary does not know about. A restore treats it as a cross-platform restore and warns accordingly.

The guarantee was added in v0.9 (ADR-0007), before the freeze, for the obvious reason: after v1.0 there is no way to give it to binaries already installed.

Treated as a supported interface, documented in JSON.md, and governed by the same additive rule: fields may be added, never renamed or removed, within v1.x. A consumer must ignore fields it does not recognise.

Two properties scripts may rely on: errors are never JSON (stdout stays empty, the diagnostic goes to stderr, exit status is non-zero), and absent optional values are null rather than omitted.

Rivet’s MSRV is 1.88. It is declared in [workspace.package] and enforced by CI’s msrv job, which reads the version out of Cargo.toml and builds every target with exactly that toolchain — so the number on this page is one that has compiled, not one that was hoped for.

This matters to you if you install with cargo install rivetsnap-cli, build from source, or depend on the library crates. It does not matter if you use a prebuilt binary or the Homebrew tap.

Change Version bump Example
MSRV stays the same Patch or minor Most releases
MSRV rises Minor, never patch A dependency raises its own floor
MSRV falls Any Never a breaking change

Raising the MSRV in a patch release would break a build that worked yesterday on a toolchain the user did not change, so it is treated as a feature-level change and gets a CHANGELOG entry naming the new floor.

An MSRV that only holds for src/ is not one a contributor can work against, so the CI job builds --all-targets: tests and benches must compile on it too.

Be explicit about this, because “1.0” is often read as “all of it is stable”.

  • The Rust library crates. rivetsnap-core, -platform, -backend, -plugins, -crypto, and -secretstores are published so that plugin authors and downstream tools can build against the same types, but their Rust APIs are not covered by this policy in the v1.x line. Only the rivet binary’s behaviour and the four on-disk formats are. If you depend on the libraries, pin an exact version.

  • CLI output intended for humans. Prose, colour, table layout, and progress rendering may change in any release. Parse --json, never stdout.

  • ~/.local/state/rivet/. Restore selections, rollback copies, and caches are local working state, not an interface. Its layout may change.

  • SECRET_SCAN_VERSION (currently 1). This is a safety counter, not a format: it rises whenever the credential scanner’s rules change, and a manifest carrying an older value simply forces a full re-scan. Rising is always safe and is expected within v1.x.

  • The package map. Curated and Repology-derived mappings change as distributions do. That is the point of it.

If something frozen here has to go in 2.0, it gets, in order: a CHANGELOG entry naming it, at least one v1.x release that warns at runtime when the deprecated form is used, and a documented migration path. Nothing frozen disappears without all three.

A v1.x Rivet failing to read or restore a snapshot written by another v1.x Rivet is a bug, not a limitation — report it. If the failure involves the vault or a sealed secret, use private vulnerability reporting rather than a public issue.