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 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
Resolve the payload version:
TEBAKO_<TOOL>_VERSIONenv → nearest.tebako-tools.yamlwalking up from cwd (per-project pinning) → user default (tebako use <tool>@<version>) → the registry's default/stable. - 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.2or a per-project pin, and the same payload just runs on the newer ruby. - 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
SHIPPEDWith 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.