Skip to content

ARCHITECTURE · 08

Runtime as image.

A tebako runtime is not one binary. It is two artifacts, an interpreter executable and an env .tfs image, downloaded once, sha256-verified, and mounted, never extracted, into the same process as the payloads it runs.

Note

The Rust driver mounts the env image and payload images in one process, and contract-2 runtimes ship from the runtime factory.

The windows exec and locking port is planned: the handoff is shipped on macOS and Linux, and the windows port is still landing.

Why the runtime is two artifacts

The interpreter changes on the ruby release line, and the files it runs against (stdlib, gems, and its own layout declaration) change with it, but they are content, and content wants different treatment than a process entry point. Splitting the pair buys four things:

Share

The pair is downloaded once per machine into the shared cache and reused by every package that names it, never duplicated into each package’s bytes.

Verify

The image is a first-class content object with its own sha256 trust anchor, verified at install and re-verified only on re-fetch, never per run.

Mounting, not extraction

The cache holds immutable artifacts only, with no extracted layout tree. At run time the driver mounts the image whole; nothing materializes a mutable copy of an immutable artifact.

Release independently

The runtime factory publishes the pair per version × platform triplet, with its own manifest and checksums. The product consumes the published release, and it never rebuilds a runtime.

Two artifacts, one runtime

Note

The pair is dual-published as of the image era.

Every image-era release of tebako-runtime-ruby publishes the pair plus a machine index (manifest.json, carrying an image key and the contract_version) and SHA256SUMS:

A · the interpreter — tebako-runtime-<ver>-<ruby>-<triplet>[.exe]

The interpreter is the process entry point, statically linked against the Rust runtime driver (crates/tebako-driver; the v1 C++ tebako-main.cpp is retired). At exec it is handed the path of its env image and mounts it instead of any embedded image.

B · the env image — tebako-runtime-<ver>-<ruby>-<triplet>.tfs

The image holds the runtime’s files (lib/ruby, gems, and everything the interpreter’s load paths expect) plus the pair declaration /lib/tebako/layout.yaml. It is installed read-only (0444) and mounted whole at the runtime root.

The pair, the handoff, the boot

The exact wire grammar is the runtime driver contract, and the loader’s side of it is the launcher ABI. From cache entry to running interpreter:

THE CACHE ENTRY — ~/.tebako runtimes/ruby-3.3.7-0.16.0-macos-arm64/ the interpreter tebako-runtime-… · 0755 the env image tebako-runtime-….tfs · 0444 sha256 / origin — exe markers .tfs.sha256 — the trust anchor: verified at install, never per run .tfs.origin — the fetch URL artifacts only — no extracted tree THE HANDOFF — exec bootstrap · the shim · tebako run env TEBAKO_RUNTIME_IMAGE=<cached .tfs> argv --tebako-image   <self|path>:<slot|->:<mount> … --tebako-entry <argv0> <args…> policy TEBAKO_JAIL / TEBAKO_JAIL_SOURCE never returns on success — the process becomes the runtime THE BOOT — ONE PROCESS 1  validate TEBAKO_MOUNT_ROOT's form malformed → exit 65, naming the variable 2  mount the env image WHOLE at the runtime root — /__tfs__ (A:/t on windows) 3  verify the pair: layout.yaml vs the exe mismatch → exit 78, before any payload 4  mount payload triples in argv order exclusive onto occupied → EEXIST, by name 5  install the jail — after the mounts malformed TEBAKO_JAIL → exit 73 6  rewrite argv to the entry; the interpreter starts Any failure along the way unmounts everything — never a partial mount. The env image is mounted, never extracted; the mount table is longest-prefix, nested mounts are legal, and payload images stay read-only (writes fail EROFS). the pair check (3) runs post-mount, before any payload or interpreter touch

Figure 1 — From cache to boot: the cache entry, the exec handoff, and the driver’s boot sequence in one process.

The handoff, stated exactly

Whoever launches the runtime (the bootstrap of a stitched package, the shim in managed mode, or tebako run) exports TEBAKO_RUNTIME_IMAGE=<absolute path of the cached .tfs> and execs the interpreter. The driver mounts that image whole at the runtime root as the first mount, then the payload triples in argv order; the image-era env var is a bare path mounted whole, the - semantics without a triple. On the standalone path the trailer’s runtime reference carries the bare ;image flag that tells the loader to resolve and verify the image alongside the executable.

Generations meet and fail closed. A driver that implements the runtime driver contract’s grammar declares contract version 2, compiled in as TEBAKO_CONTRACT_VERSION and declared in the release manifest, and the two must agree. The loader negotiates before any checksum acceptance: an unsupported contract is exit 75, naming both generations and the remedy, to upgrade tebako or pin an older runtime, and it is never a silent accept.

The mount root has one owner

The runtime root (ruby: /tfs on POSIX and A:/t on windows, kept short by owner decision for MAX_PATH headroom) is a contract value, so it has exactly one authoritative source: the patch literals in tamatebako/ruby. Everything downstream flows it, and nothing re-authors it (the SSOT invariant):

tamatebako/ruby patch literals — THE OWNER the only hand-written copy source tarball tebako-mount-root manifest C1 · absent/corrupt → exit 132 runtime factory flows it — never re-authors C2 · release card carries it the exe compiled-in root (entry TU) the env image /lib/tebako/layout.yaml C3 · one factory pair the driver compares post-mount, before ruby init mismatch → exit 78 A mismatched pair is refused with both values printed — never a ruby LoadError from a half-right mount. On windows the driver qualifies every declared mount onto the root's drive — the wire grammar never carries one. the in-image declaration grammar: docs/spec/schemas/layout.yaml — owned there, mirrored by crates/tebako-driver

Figure 2 — The mount-root flow: patch literals in tamatebako/ruby flow through the source tarball and runtime factory into the exe and env image, and the driver verifies the pair at every boot.

The exe’s compiled-in root and the image’s declared mount_root (in /lib/tebako/layout.yaml) are two flowings of the same literal, built as one pair. The driver checks them against each other on every boot: that is what makes a stale or mismatched pairing a named refusal instead of a broken load path.

Moving the root: TEBAKO_MOUNT_ROOT

Note

The override is shipped, locked 2026-08-08.

The baked root is the per-platform default, not the only spelling. When TEBAKO_MOUNT_ROOT is set in the runtime’s environment, the driver mounts the env image at that root instead, and everything downstream follows: era-2 rbconfig emits ENV["TEBAKO_MOUNT_ROOT"] || <baked>, the io-routing patches read the driver-reported root, and declared payload mounts qualify onto the override’s drive exactly as they do the baked root’s.

Two gates keep the override honest, in order:

Gate 1 — form, before any mount

The value must be an absolute path (/… or drive-qualified X:/…) with no trailing slash and no ... A malformed value is exit 65, naming the variable, before anything mounts.

Gate 2 — grant, post-mount

The image must declare mount_root_override: true (layout schema_minor 1). An image without the grant predates the override era, because its rbconfig is pinned to the baked root, so the driver refuses with exit 78, naming both the override and the image. There is never a boot whose load paths point at an unmounted root.

The cache entry

Under ~/.tebako, a runtime entry holds immutable artifacts and their trusted markers, nothing else:

runtimes/<lang>-<lv>-<ver>-<triplet>/
  tebako-runtime-<ver>-<lv>-<triplet>[.exe]   # interpreter (0755)
  sha256 / origin                             # executable markers
  tebako-runtime-<ver>-<lv>-<triplet>.tfs     # env image (0444)
  <image>.sha256                              # trust anchor: "<sha>  <file>\n"
  <image>.origin                              # the URL it was fetched from

The .sha256 marker is the trust anchor: its presence means the artifact was sha256-verified at install, and it is re-verified only when re-fetched, never per run. Installs are tmp + rename under a per-entry flock, so a partial install is invisible, and a checksum mismatch on download deletes the download and leaves the cache untouched.

The one place extraction still exists is press time, and it is not a cache shape: when the packager needs the runtime’s files as a build environment, it extracts the cached image in-process through the TFS ABI into the build prefix, rebuilt per press and never stored back. The run side mounts, and it never extracts.

Why .tfs and not .dwarfs

The env image’s metadata is serialized with FlatBuffers, the dwarfs-t writer’s default in these builds, which upstream DwarFS cannot read. A format that upstream cannot open does not get upstream’s extension: .tfs marks a TFS image (content-detected by the reader regardless of extension), while .dwarfs stays reserved for upstream-compatible images. The writer binding lives in dwarfs-t-rs, and image creation is in-process, never a shelled mkdwarfs.

Format is orthogonal to role: the extension and the format_id answer exactly one question, how to read these bytes. Payloads pressed today default to the limnifs format (since CLI v0.2.0), and the runtime factory’s env images are dwarfs-t-native. Both are .tfs, and the runtime contract on this page does not change either way.

Edge cases

Situation What actually happens

boot without TEBAKO_RUNTIME_IMAGE

The shape is legal, a bare interpreter, but the driver names the absence on stderr (stdout belongs to the payload). In managed mode a missing handoff is a launcher bug, surfaced loudly, and there is no silent fallback onto an embedded copy.

exe from one build, image from another

The boot is refused by name: the pair check compares the image’s declared mount_root against the exe’s compiled-in value post-mount, before the interpreter inits, giving exit 78 with both values printed and never a ruby LoadError.

writes into a mounted image

The write fails with EROFS. Payload and env images are read-only forever; write and encryption overlays live only in the Rust TFS layer, and dwarfs-t never learns to write (the transforms law).

windows today

The handoff is shipped on macOS and Linux, and the windows exec and locking port is partial (roadmap 02). The contract itself (roots, overrides, and exit codes) is platform-uniform by design.