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
Platformimpls; 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::detectstays an associated fn resolved by a registry, not a trait-object method.
Context
Section titled “Context”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.
Decision
Section titled “Decision”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, thinPlatform 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.
Alternatives considered
Section titled “Alternatives considered”- Keep
matchon 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.
Consequences
Section titled “Consequences”- Positive: new distros/managers are additive;
rivet-corestays pure and fully unit-testable; macOS/Windows later are “just” newPlatformimpls. - 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.