Skip to content

ADR-0005 — Cross-distro packages

  • Status: Accepted (2026-07-24, Opus review)
  • v0.9 note (2026-07-25): the predicted absorption happened. brew is now a column in the curated map and Repology’s homebrew repo feeds the generated one (ADR-0007). Homebrew casks are deliberately excluded: a macOS application bundle has no Linux counterpart, so mapping one would be exactly the silent guessing this ADR rejects.
  • Related: ROADMAP v0.3, CODE_REVIEW M5, ADR-0001
  • Acceptance note: Confirmed. v0.2 keeps PackageManifest byte-stable on disk (no unification, no new fields) to preserve identical behaviour; the logical field and the mapping table are introduced in v0.3, added as serde #[serde(default)] so older snapshots stay readable. Within-family restore is the guaranteed floor; cross-family is best-effort-with-warnings, never silent guessing.

Once Rivet discovers packages via multiple PackageManagers (ADR-0001), a hard truth surfaces: the same software has different package names on different distros. fd is fd-find on Debian; ripgrep’s binary is rg; Python is python/python3/python311 depending on the distro. A naive “install the Arch names on Fedora” restore will partially and silently fail. This is the central difficulty of OS-agnostic environment restore, so it needs an explicit policy rather than an accidental one.

Model a logical package distinct from a manager-specific name, and adopt a conservative default: perfect fidelity within a distro family, honest warnings across families — never silent guessing.

pub struct Package {
pub manager: String, // "pacman", "apt", ... (as captured)
pub name: String, // manager-native name (as captured)
pub logical: Option<String>,// resolved cross-distro identity, if known
}

Resolution layers, in order:

  1. Same family (default, always exact). Arch→Arch, Debian→Ubuntu, etc. restore uses the captured native names verbatim. No mapping risk.
  2. Curated mapping table (shipped data, community-extendable) keyed by logical id → per-manager name, e.g. ripgrep = { pacman="ripgrep", apt="ripgrep", dnf="ripgrep", brew="ripgrep" }, fd = { pacman="fd", apt="fd-find", dnf="fd-find", brew="fd" }. Only high-confidence entries are included.
  3. Unresolved → warn, never guess. A package with no mapping for the target manager becomes a restore-plan warning (“foo: no known Fedora equivalent — install manually or add a mapping”), listed but not attempted.

The mapping table is data, not code (a versioned TOML/JSON asset), so contributors extend coverage without touching logic, and users can add local overrides in rivet.toml. MachineMetadata records distro family+version so restore knows when it is crossing a boundary and should surface warnings.

Cross-family restore is therefore opt-in-honest: it does as much as it can confidently, and clearly reports the rest instead of producing a broken machine that looks complete.

  • Guess by fuzzy name matching. High risk of installing the wrong package; violates “understand, don’t blind-copy.” Rejected.
  • Same-family only, no mapping ever. Simple and safe, but abandons the OS-agnostic promise for the (common) case of migrating Arch→Fedora. Rejected as the ceiling; adopted only as the guaranteed floor.
  • Depend on an external universal-package DB (repology, etc.) at runtime. Great data, but a network dependency and licensing/availability coupling at the worst moment (a fresh machine mid-migration). Instead, optionally generate our shipped table from such sources offline at build time.
  • Push everything to Flatpak/Nix for portability. Elegant but changes the user’s chosen package model; offered as an option, not imposed.
  • Positive: honest, safe cross-distro restore; contributors grow coverage via data PRs; same-family migrations are perfect.
  • Positive: the same logical concept later absorbs Homebrew/winget for macOS/Windows.
  • Cost: an ongoing mapping table to curate; partial cross-family restores by design (with clear warnings) rather than a false promise of completeness.
  • Risk: users may expect 100% cross-family fidelity — set expectations in docs: Rivet guarantees within-family, best-effort-with-warnings across.