rialoman 0.3.0

Rialo native toolchain manager
Documentation
# 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:

| Channel | Version Format | Example | Use Case |
|---------|---------------|---------|----------|
| `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

| Component | Responsibility |
|-----------|----------------|
| **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

| Item | Benefit | Priority |
|------|---------|----------|
| 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

| Approach | Pros | Cons |
|----------|------|------|
| 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

| Approach | Pros | Cons |
|----------|------|------|
| **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/`.

| Approach | Pros | Cons |
|----------|------|------|
| **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

| Decision | Rationale |
|----------|-----------|
| 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`