Skip to content
tonythethompson edited this page Jul 10, 2026 · 1 revision

Immutable Payloads

Immutable Payloads represent the foundational storage and integrity strategy in Numan. The system ensures that every package installation—whether a plugin, module, script, or completion—is landed under a versioned, content-addressed path that is never overwritten in place. This approach guarantees reproducibility and safety across Nushell upgrades.

The design principles dictate that the installation process is entirely inert, writing only to the $NUMAN_ROOT and deferring any actual Nushell integration to a separate activation phase. By pinning payloads to unique, content-derived identifiers, Numan maintains a clear separation between different versions and origins of the same package.

Architecture and Path Structure

Payloads are stored within the Numan root directory using a deterministic naming convention that includes the package type, owner, name, and a unique version-hash suffix. This structure ensures that updates do not replace existing files but instead land in a new, side-by-side directory until a garbage collection (GC) process removes unreferenced versions.

Payload Path Template

The standard path for an immutable payload follows this shape: <root>/packages/<type>/<owner>/<name>/<version>-<sha8prefix>/

Storage Layout Diagram

The following diagram illustrates the relationship between the Numan root and the immutable package storage.

flowchart TD
    Root[$NUMAN_ROOT] --> Packages[packages/]
    Packages --> Plugins[plugins/]
    Packages --> Modules[modules/]

    Plugins --> P_Owner[owner/]
    P_Owner --> P_Name[name/]
    P_Name --> P_V1[1.0.0-sha1/]
    P_Name --> P_V2[1.1.0-sha2/]

    Modules --> M_Owner[owner/]
    M_Owner --> M_Name[name/]
    M_Name --> M_V1[0.5.0-sha3/]
Loading

The diagram shows how multiple versions of the same package (e.g., v1.0.0 and v1.1.0) coexist as distinct directories under the same owner/name hierarchy.

Integrity and Content Addressing

Numan uses SHA-256 manifest hashing to verify the integrity of payloads. A unique revision_id is computed for every installed payload directory to ensure the contents remain unchanged after promotion to immutable storage.

The Revision ID

The revision_id is a manifest hash of the installed payload directory. It is computed by:

  1. Walking every file in the payload recursively.
  2. Building a sorted list of entries formatted as path:sha256:kind.
  3. Calculating the SHA-256 hex digest of this manifest string.

Lockfile Data Model

The lockfile acts as the ground truth for which specific immutable payloads are active.

Field Type Description
payload_path String Relative path from root to the immutable payload directory.
revision_id Option Manifest hash of the payload directory contents.
payload_sha256 Option SHA256 of the original downloaded payload archive.
executable_sha256 Option SHA256 of the specific plugin binary (plugin types only).
origin Option Origin of the install (e.g., registry:official, nupm_import).

Implementation Logic

The installation transaction ensures that payloads are extracted and verified before being promoted to their final immutable path.

Lifecycle of a Payload

The following sequence diagram represents the flow of a package from download to immutable storage in the lockfile.

sequenceDiagram
    participant T as Transaction
    participant FS as File System
    participant L as Lockfile

    T->>FS: Download and Verify Archive
    T->>FS: Extract to Temporary Staging
    T->>T: Compute revision_id (Manifest Hash)
    T->>FS: Promote Staging to Versioned Path
    T->>L: Write Entry with payload_path and revision_id
    Note over T,L: Payload is now Immutable
Loading

Verification Code Snippet

The following logic defines how the revision_id is derived to ensure cross-platform determinism by normalizing path separators.

// src/state/lockfile.rs:276-282
let rel_str = rel
    .components()
    .map(|c| c.as_os_str().to_string_lossy().into_owned())
    .collect::<Vec<_>>()
    .join("/");
let content = match std::fs::read(&path) {
    Ok(b) => b,
    Err(_) => continue,
};
let sha = compute_sha256(&content);

Lifecycle Operations and Garbage Collection

Because payloads are immutable and never overwritten, operations like update or remove do not immediately delete files. Instead, they modify the lockfile's pointers.

  • Update: Downloads the new version into a new side-by-side immutable directory.
  • Remove: Deletes the reference from the lockfile. The payload remains on disk until a GC operation.
  • Garbage Collection (GC): Scans the packages/ directory and deletes any payload directories not referenced by the current lockfile or any active activation snapshots.

nupm Compatibility and Imports

When importing packages from nupm, Numan maintains its immutability contract by creating a physical copy of the source files. Numan does not symlink to the nupm source; it creates a new immutable revision in the Numan root. This ensures that subsequent changes in the nupm source tree do not affect the Numan installation until an explicit re-import is performed.

Immutable payloads provide Numan with the ability to perform safe rollbacks and handle multiple Nushell versions simultaneously, as every package version exists in a unique, verified location on the filesystem.

Clone this wiki locally