Skip to content

ARCHITECTURE · 09

The version manager.

The platform play: tebako stops being only a packager and becomes a program–version–runtime manager. A tebako package was a shim all along — this generalizes it.

SHIPPED

Shipped in v2.0.0. The tebako-shim dispatcher, the registry format, and the store described below are live intebako-rs — including zero-runtime native payloads (hello, Inkscape, a JRE) and the install-time materialization they exec from.

Four artifacts, four jobs.

A lean tebako package already is a dispatcher: bootstrap + trailer + images, resolving its runtime at run time. The version manager splits that trick into four independent artifacts:

PAYLOAD

A signed .tfs image of the app — versioned, runtime-independent (when pure-ruby), immutable.

RUNTIME

A signed Ruby runtime — versioned, cached, machine-shared. The same artifacts the runtime releases publish today.

REGISTRY

A developer-hosted manifest on their own GitHub releases — tpkg-registry.json listing payload versions, digests/signatures, runtime requirements, and the default. Zero central infrastructure.

DISPATCHER

tebako-shim — a tiny static Rust binary (same size-audited discipline as the bootstrap), linked per tool into ~/.tebako/shims/<tool>. The thing on PATH that picks version + runtime per invocation and hands off.

The dispatch chain, per invocation.

  1. 1

    Resolve the payload version: TEBAKO_<TOOL>_VERSION env → nearest .tebako-tools.yaml walking up from cwd (per-project pinning) → user default (tebako use <tool>@<version>) → the registry's default/stable.

  2. 2

    Resolve the runtime: the payload's declared constraint → newest compatible runtime already cached — no download — else download the newest compatible from the runtime releases. Swapping runtimes never touches the immutable payload: tebako use --runtime ruby@3.4.2 or a per-project pin, and the same payload just runs on the newer ruby.

  3. 3

    Hand off: exec the cached payload image against the chosen runtime — the lean path: mount payload + runtime, exec. Signed payloads are verified at install time, not per run.

The compatibility model — the honest constraint.

PURE-RUBY PAYLOADS

The runtime constraint is a range — ruby >= 3.3, < 5.0. Any newer runtime works; older payloads just run faster on newer rubies. One universal .tfs serves every platform — the dispatcher's per-platform runtime does the rest.

NATIVE-EXTENSION PAYLOADS

Locked to the ruby ABI line they were built against — the constraint records ruby ~> 3.3.0. A newer line requires a new payload build. Per-platform and per-ABI-line artifacts; a wrong-line runtime produces a named compatibility error, never a segfault.

The registry manifest carries the constraint per payload version as additive keys — older consumers ignore them. tpkg files themselves remain platform-dependent (bootstrap + runtime are native; the trailer is platform-agnostic bytes).

Shell integration — no per-shell magic.

One directory on PATH: ~/.tebako/shims. One-time setup — the CLI offers to append the export line to your .zshrc/.bashrc, like pyenv's installer. And unlike rbenv's eval "$(rbenv init -)", there is no eval-init hook for switching: the dispatcher reads the project file itself (the mise model). tebako use / disable / list / doctor manage the shims.

Two distribution forms, one press.

tebako press already produces the payload. A developer's release flow is: press → sign → upload the .tfs + update the registry manifest (a small tebako publish helper later; manual first). Users without tebako get the standalone per-platform tpkg executable; dispatcher users get the registry payload — one universal image for pure-ruby apps, per-platform variants only for native-ext apps. Building all of them is a matrix CI job, not manual work.

The mnenv retirement gate

SHIPPED

With tebako-shim shipped, mnenv — metanorma's Ruby version manager — retires, and metanorma becomes the first real dogfood consumer: a heavy, native-extension, real-world app. Its version management moves to the shim + registry model — payload builds via tebako press (including SDK native builds), signed .tfs per (version × ruby line), the registry manifest, the dispatcher. The ultimate proof of the whole stack, eaten by its own cooks.