Skip to content

ADR-0001 — Platform abstraction

  • Status: Accepted (2026-07-24, Opus review)
  • v0.9 note (2026-07-25): validated by macOS. This ADR’s central claim was that macOS and Windows would later be “just” new Platform impls; macOS needed no change to either trait. It did need one thing this ADR did not anticipate — an OS axis distinct from the Linux distro axis — added in ADR-0007.
  • Deciders: Rivet architecture review
  • Related: ROADMAP v0.2/v0.3, CODE_REVIEW M1, ADR-0005
  • Acceptance note: Confirmed. Trait shapes are frozen for v0.2 as finalized in v0.2-implementation-spec.md. Object-safety is a hard requirement (Box<dyn PackageManager> / Box<dyn Backend> / Box<dyn Plugin>); Platform::detect stays an associated fn resolved by a registry, not a trait-object method.

The Charter promises support for any Linux distro, WM/tiling manager, and package manager (then macOS, then Windows). The v0.1.1 code is hardcoded to Arch: only src/platform/arch.rs exists, and it is called directly — not through a trait — by main.rs, snapshot.rs, and restore.rs. restore::install_packages bakes in pacman/paru/yay. Adding a second distro today would mean match statements threaded through unrelated modules.

We need seams that let a new platform or package manager be added, not retrofitted, without touching the snapshot/restore workflow or core types.

Introduce three core traits in a new rivet-core crate, and refactor the existing Arch code to implement them. Convert the single package into a Cargo workspace.

/// Host identity and which package managers/desktops are present.
pub trait Platform {
fn detect() -> Option<Self> where Self: Sized; // None if not this platform
fn metadata(&self) -> MachineMetadata; // distro family+version, WM, arch
fn package_managers(&self) -> Vec<Box<dyn PackageManager>>;
}
/// One package ecosystem. Discovery is read-only; install is guarded + explicit.
pub trait PackageManager {
fn id(&self) -> &'static str; // "pacman", "apt", "dnf", ...
fn is_available(&self) -> bool; // executable present?
fn discover_explicit(&self) -> Result<Vec<Package>>; // user-installed only
fn plan_install(&self, pkgs: &[Package]) -> Vec<RestoreAction>;
fn install(&self, pkgs: &[Package]) -> Result<usize>; // may invoke sudo
}

Workspace layout (from CODE_REVIEW.md):

crates/rivet-core traits + model + policy (no side effects)
crates/rivet-platform ArchPlatform, DebianPlatform, ... PacmanManager, AptManager, ...
crates/rivet-backend (ADR-0002)
crates/rivet-crypto (ADR-0003)
crates/rivet-plugins (ADR-0004)
crates/rivet-cli today's main.rs, thin

Platform selection is “first detect() that returns Some”. PackageManager availability is checked at runtime so a machine with several managers is handled. MachineMetadata gains distro_family, distro_version, and window_manager.

  • Keep match on an enum of platforms. Simpler initially, but every new platform edits central code — the exact coupling we are removing. Rejected.
  • Dynamic (dlopen) plugins for platforms now. Over-engineered for v0.2 and a stable ABI is premature. Deferred to post-1.0 (ROADMAP ecosystem).
  • Stay single-crate with modules. Works, but a workspace enforces the “core has no I/O side effects” boundary at compile time and speeds incremental builds as crypto/backends grow. Chosen the workspace.
  • Positive: new distros/managers are additive; rivet-core stays pure and fully unit-testable; macOS/Windows later are “just” new Platform impls.
  • Positive: forces fixing CODE_REVIEW H1/H2 (modes, symlinks) inside the new collect path rather than around it.
  • Cost: a mechanical but repo-wide v0.2 refactor; snapshot format may need a read shim. Mitigated by the H4 round-trip test landing first.
  • Risk: trait shapes may need iteration once apt/dnf land — acceptable while pre-1.0 and the format is still versioned.