# Rialoman Architecture
## Overview
Rialoman is the release and toolchain manager for Rialo. It solves a fundamental tension: **releases ship frequently (commit-level), but toolchains are huge (~250MB) and change rarely**.
The solution is a **reference-based component model**:
- Releases stay small (~50MB) by referencing component versions, not bundling them
- Components (toolchains, PDKs) live in shared storage, reused across releases
- The filesystem is the coordination mechanism: no IPC, no coupling between tools
This enables two modes with zero code coupling:
- **Managed mode**: rialoman installs releases + components, rialo-build uses them
- **Standalone mode**: core contributors run `cargo run --bin rialo-build` without rialoman
### Design goals
- **Deterministic installs**: manifest pins exact versions
- **Small releases**: shared components reduce download size
- **Standalone-friendly**: tooling works without rialoman
- **Filesystem-based coordination**: explicit, inspectable state on disk
---
## Core Concepts
### Releases vs Toolchains
Two separate things, managed separately, but coordinated via manifest:
- **Releases**: Channel-versioned packages of binaries (rialo, rialo-node, rialo-build). Specified as `channel@version` (e.g., `stable@1.0.0`, `nightly@2025-01-14`, `commit@abc123`).
- **Toolchains**: The Rust compiler used to build programs (rialo-rust). Version-only, no channels.
Releases reference toolchain versions in their manifest. When you install or use a release, rialoman can install the matching toolchain (opt-out with `--no-toolchain`).
### Channels
Releases use three channels:
| `stable` | Semver | `stable@1.0.0` | Production |
| `nightly` | Date | `nightly@2025-01-14` | Latest features |
| `commit` | Git hash (8+ chars) | `commit@abc123` | Specific builds |
Toolchains do NOT have channels - just versions (e.g., `rialo-rust 0.0.1`).
### Manifest = "We Tested This"
The release manifest is a **public obligation**. When we ship `rust_toolchain: "0.0.1"`, we're saying: "this release was validated with this exact toolchain version."
The manifest also enumerates release artifacts (archives, binaries, files, directories). It is the contract for what a release contains and which toolchain it expects.
Users get what we tested, not "latest compatible". This drives:
- Exact versions, not semver ranges
- No automatic version resolution
- Coordination happens at release time, not runtime
### `rialo` is the Gateway, `rialoman` is the Plumber
**rialo** is the entry point - the unified CLI for the full Rialo development lifecycle (keys, client, network ops, Venus projects). It's the command developers call daily - like `cargo` for Rust.
**rialoman** is infrastructure. It installs releases, manages toolchains, and maintains shims in `$RIALO_HOME/bin/`. Users run it once during setup, occasionally for updates - like `rustup` for Rust.
The separation matters:
- `rialo` stays focused on developer workflows
- `rialoman` handles platform concerns (downloading, versioning, PATH management)
- Neither needs to know about the other's internals
### Filesystem as Coordination
Both rialoman and rialo-build-lib read/write to the same toolchain storage location. By default this is `$RIALO_HOME/toolchains/`, but rialo-build-lib also honors `RIALO_BUILD_TOOLCHAIN_HOME` for standalone workflows.
```
rialoman rialo-build (standalone)
│ │
│ install("0.0.1") │ build("./contract")
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ rialo-build-lib │
│ ~/.local/share/rialo/toolchains/rialo-rust-0.0.1/ │
│ (SHARED storage - both tools read/write here) │
└─────────────────────────────────────────────────────────────┘
```
---
## Distribution Model
There are two distinct distribution paths:
1. **Release artifacts** (manifests + binaries)
- Hosted under: `https://rialo-artifacts.s3.us-east-2.amazonaws.com/binaries/{channel}/{version}/`
2. **Toolchains** (runtime downloads)
- Hosted under: `https://rialo-artifacts.s3.us-east-2.amazonaws.com/toolchains/{toolchain}/{version}/`
Toolchain upload/download is also supported via an AWS-credentialed S3 backend for CI and release engineering.
### Artifact Layout
Each component (e.g., `rialoman`, `binaries`) is published under `{component}/{channel}/{version}/`:
```
rialoman/
├── commit/
│ ├── abc12345/ # Immutable, per-commit
│ │ ├── manifest.json
│ │ ├── rialoman-commit-abc12345-linux-amd64.tar.gz
│ │ └── ...
│ ├── latest/ # Rolling alias → most recent commit
│ │ ├── manifest.json
│ │ ├── rialoman-commit-latest-linux-amd64.tar.gz
│ │ └── ...
│ ├── latest.json # Copy of latest/manifest.json at channel root
│ └── install.sh # Bootstrap script (channel = commit)
├── stable/
│ ├── 1.0.0/ # Immutable, per-release
│ │ ├── manifest.json
│ │ └── rialoman-stable-1.0.0-linux-amd64.tar.gz
│ ├── latest/ # Rolling alias → most recent stable
│ │ ├── manifest.json
│ │ └── rialoman-stable-latest-linux-amd64.tar.gz
│ ├── latest.json # Copy of latest/manifest.json at channel root
│ └── install.sh # Bootstrap script (channel = stable)
```
The channel and version are baked into **tarball filenames**, **manifest.json** (channel, version, artifact paths), and **install.sh** (RELEASE_CHANNEL). This makes every URL fully constructible from `(component, channel, version, platform)` without any prior state.
### Bootstrap Installer (`install.sh`)
The bootstrap installer (`tools/rialoman/scripts/install.sh.template`) gets the channel baked in at publish time and is uploaded per-channel to `{component}/{channel}/install.sh`.
The `latest/` directory with deterministically-named tarballs exists so the install script can construct a direct download URL from just `(channel, "latest", platform)` — a single HTTP GET, no JSON parsing required. This matters because the bootstrap host may only have `curl` and `tar`; we cannot assume `jq` or any JSON parser is available.
Once rialoman is installed, it can afford a two-hop path: fetch `{channel}/latest.json` for the manifest (needed for SHA verification and toolchain metadata), then download via `artifact.path`.
---
## Architecture
```
┌────────────────────────────────────────────────────────────────────────┐
│ Public HTTPS Artifacts │
│ binaries/{channel}/{version}/manifest.json + archives │
│ toolchains/{toolchain}/{version}/{archive}.tar.gz │
└───────────────────────────────┬────────────────────────────────────────┘
│
┌───────────────▼───────────────┐
│ rialoman │
│ (Release/Toolchain Mgr) │
│ - install/use/list releases │
│ - toolchain subcommands │
│ - manages $RIALO_HOME │
└───────────────┬───────────────┘
│
┌───────────────────────▼───────────────────────┐
│ $RIALO_HOME/ (see Directory Layout) │
│ ├── bin/ (symlink shims) │
│ ├── releases/ (channel/version dirs) │
│ ├── toolchains/ (rialo-rust-X.Y.Z/) │
│ └── current.json (active release pointer) │
└───────────────────────┬───────────────────────┘
│
┌───────────────▼───────────────┐
│ rialo-build │
│ (Build tool, part of │
│ release binaries) │
└───────────────┬───────────────┘
│
┌───────────────▼───────────────┐
│ rialo-build-lib │
│ - Auto-detect program type │
│ - Toolchain::install() │
│ - RiscvBuilder/SolanaBuilder │
└───────────────────────────────┘
```
### Components
| **rialoman** | Release management, shim management, orchestrates toolchain installation via rialo-build-lib |
| **rialo-build-lib** | **Owns toolchain logic**: detection, installation, resolution, rustup registration. Shared by rialoman and rialo-build |
| **rialo-build** | CLI wrapper for rialo-build-lib; delegates toolchain decisions to the library |
---
## Runtime Flows
### Managed mode (rialoman)
1. `rialoman install` or `rialoman use`
2. rialoman fetches release manifest + archive
3. rialoman extracts release under `$RIALO_HOME/releases/{channel}/{version}`
4. rialoman updates `$RIALO_HOME/current.json`
5. rialoman updates shims in `$RIALO_HOME/bin`
6. rialoman installs toolchain from manifest (unless `--no-toolchain`)
### Standalone mode (no rialoman)
1. Developer runs `cargo run --bin rialo-build` (or just `rialo-build`)
2. rialo-build-lib resolves toolchain from env → manifest → installed
3. Toolchain is used from shared storage (`$RIALO_HOME/toolchains` or `RIALO_BUILD_TOOLCHAIN_HOME`)
---
## Toolchain Resolution
Resolution order in `RialoRustToolchain::new()`:
```
1. Environment var: RIALO_RUST_TOOLCHAIN_VERSION
2. Release manifest: $exe_dir/../manifest.json → rust_toolchain field
3. First installed: Any installed rialo-rust toolchain
```
This chain ensures: explicit override → rialoman-managed → standalone fallback.
Rialoman also resolves the active toolchain via `current.json → releases/{channel}/{version}/manifest.json` for `toolchain install --current`.
---
## Manifest Detection
How does rialo-build-lib know if it's running from a rialoman-managed release vs standalone?
It looks for `../manifest.json` relative to its executable:
```
$RIALO_HOME/releases/stable/1.0.0/
├── bin/rialo-build ← executable here
└── manifest.json ← looks for this (../manifest.json)
```
If found, it reads `rust_toolchain` from the manifest and uses that version. If not found (standalone mode), it falls back to the first installed toolchain. This is how the two modes work without any coupling.
---
## Shim Lifecycle
Shims are **symlinks** created in `$RIALO_HOME/bin/` pointing to binaries in the active release. They are updated whenever `rialoman use` switches releases, and may be overwritten to match the new active release. Shims are managed solely by rialoman.
---
## Directory Layout
```
$RIALO_HOME/
├── bin/ # Shims (symlinks to active release binaries)
├── releases/ # Installed releases
│ └── stable/1.0.0/
│ ├── bin/
│ └── manifest.json
├── toolchains/ # Shared toolchains (the key insight!)
│ └── rialo-rust-0.0.1/
├── downloads/ # Cached tarballs + manifests
├── tmp/ # Staging for atomic installs
└── current.json # Active release pointer
```
---
## Open Questions & Future Direction
### Consider: Merge rialo-build and rialo-build-lib
Currently split into two crates:
- `crates/rialo-build-lib/` - Library (toolchain logic, builders)
- `tools/rialo-build/` - CLI binary (thin wrapper)
Could merge into single crate with both `lib.rs` and `main.rs` (standard Rust pattern like cargo, ripgrep). Benefits: simpler dependency graph, clearer ownership, one less crate to manage. rialoman would depend on `rialo-build` as a library instead of `rialo-build-lib`.
### Unsettled: Component Binary Discovery
Components may contribute binaries (e.g., `sol-pdk` → `rialo-sol-idlgen`). How are these discovered?
**Leaning**: Install to release dir, update shims (release-isolated, aligns with current architecture).
### Unsettled: Shim Lifecycle
Who owns shims when components are added/removed after release install?
**Proposed**: rialoman owns all shims; `rialoman component install X` updates them.
### Future: Component System
The reference-based model extends naturally to all large artifacts:
```json
{
"manifest_version": 2,
"components": {
"rust_toolchain": "0.0.1",
"sol_pdk": "1.0.0",
"gnu_riscv": "13.2.0"
},
"profiles": {
"default": ["core", "build", "rust_toolchain"],
"minimal": ["core"],
"full": ["core", "build", "rust_toolchain", "sol_pdk"]
}
}
```
This solves: node operators get minimal installs, developers get full tooling, CI caches components independently.
### Near-term Priorities
| Toolchain compatibility warning on `rialoman use` | High | **P0** |
| `rialo build` command (exec wrapper) | Medium | P1 |
| Plugin discovery (`rialo X` → `rialo-X`) | Medium | P2 |
---
## Design Decisions (ADR)
### Hybrid Toolchain Installation
| rialo-build (on-demand) | Lazy install | Delay mid-build |
| rialoman (upfront) | Ready state after install | Installs even if unused |
| **Hybrid (chosen)** | Flexibility | Dual code paths |
**Rationale**: Covers all users - developers get ready state, CI gets explicit control, node operators skip entirely.
### Reference-Based (not Bundled) Toolchains
| **Reference-based** | ~50MB releases, shared storage, CI-cacheable | Needs network first time |
| Bundled | Self-contained | ~300MB releases, duplicated |
**Rationale**: Toolchains change rarely; releases change frequently. Same pattern extends to all components.
### No Post-Extraction Mutation (Release Contents)
When rialoman extracts a release, the release contents are immutable. No configs or manifests are rewritten. The only mutation after extraction is **shim updates** in `$RIALO_HOME/bin` to point at the active release.
**Rationale**: Simplifies debugging ("what you see is what you get"), rollback (just switch pointer), and Windows support.
### JSON Pointer over Symlinks
Active release is tracked via `current.json` (contains channel + version), not a symlink like `$RIALO_HOME/current → releases/stable/1.0.0/`.
| **JSON file** | Cross-platform; atomic updates; can store metadata | Extra read on lookup |
| Symlink | Direct filesystem reference | Windows requires admin; breaks across drives; no metadata |
**Rationale**: Symlinks are problematic - Windows needs elevated permissions, they can't span drives, and they complicate debugging. A JSON file is portable, atomic, and can evolve to store additional state.
### Other Decisions
| Keep download logic in rialo-build-lib | Enables standalone mode; ~100 lines acceptable |
| Single `cargo +rialo` registration | Sufficient; env var for overrides |
| Auto-install toolchain on `rialoman use` | Matches `install` behavior; opt-out with `--no-toolchain` |
---
## Source Pointers
- `tools/rialoman`
- `crates/rialo-build-lib`