ARCHITECTURE · ANATOMY
How a Tebako package works.
One file, three parts, no installation. This is the whole mechanism — what the file contains, how the parts meet at run time, and what happens when something does not fit.
Three parts, glued by a trailer.
A tebako package is a composition of independently distributable parts. Each part has its own release line, its own publisher, and its own trust anchor; the executable you download is where they meet.
A · The bootstrap — the loader
A small, static Rust binary — under 3 MB, a budget enforced in CI on every platform — that is the process entry point of every package. It reads the trailer at the end of its own file, verifies what it finds, resolves the runtime (from the machine-wide cache, or by downloading it once), and hands off. It carries its own HTTP, TLS, and archive handling: nothing shells out to curl, git, or any tool on your machine. The bootstrap is a published artifact — we build it, you never do — and the same bytes are shared by every package pressed for a platform.
B · The runtime — two artifacts
A runtime is the interpreter plus everything its load paths expect, and v2 ships it as two artifacts: the interpreter executable itself, and an env image — a single .tfs file holding the standard library, gems, and support files. (The windows runtime adds a third: the shared ruby DLL the exe imports, installed beside it under its declared PE name.) The set is downloaded once per machine into the shared cache, SHA-256 verified, and mounted, never extracted. Every tebako application on the machine reuses the same copy; upgrading the runtime never rebuilds an application. Ruby is the first runtime; the model is language-agnostic.
C · The payload slices — bare images
Your application, your data, your native tools — each a bare .tfs image, self-describing through a manifest inside the image at /__tpkg__/manifest.yaml. The manifest declares what the slice is (app, data, toolkit, runtime), what it provides (commands, libraries, a runtime), and what it requires (a runtime constraint, other slices at mount points). Slices are published, versioned, and verified independently — and they co-mount into one process.
One executable, laid out.
The package file is the bootstrap followed by payload slots, a slot table, optional extension blocks, and a 166-byte trailer header at the very end — read last, first. A fat package carries its runtime in a slot and never touches the network; a lean package references the runtime and resolves it at run time.
Where the runtime comes from.
Runtimes are factory products, not local builds. An official Ruby release flows through two factories before any user machine sees it, and every boundary is a published, checksummed release — each stage consumes the previous stage's artifacts, never its source tree. The full stage-by-stage table is on the pipeline page.
How a run works.
There are two front doors and one mechanism behind them. Standalone:you execute a package file directly — the bootstrap inside reads its own trailer and resolves the runtime. Managed: you invoke a command name on PATH — a shim link to the dispatcher, which picks the payload version and the runtime per invocation. Either way, the same handoff happens at the end: the runtime executable starts with a list of images to mount and an entrypoint to run.
Two consequences worth internalizing. First, a run is a run: executing a package never installs its slices — installation is the explicit verb, tebako install. Second, verification happens at fetch and install, never per run — a cached, verified artifact starts at full speed every time.
One process, many images: the mount model.
Inside the running process there is one virtual filesystem. The env image mounts whole at the runtime root — the path the interpreter was compiled to live at — and every payload mounts at the path its consumer declared. A path lookup resolves against the longest matching mount prefix; mounts may nest; and the host filesystem is visible only where the jail allows it. Payload images are always read-only.
The consumer-declared mount is the dependency mechanism: an application's manifest says I need inkscape, this version range, at /opt/inkscape — the slice itself stays location-agnostic, and one installed copy serves every application that mounts it. The worked example on the audience page runs this end to end; the manifest grammar is on the feature surface.
Generations meet fail-closed: the contract model.
The loader and the runtime are separate artifacts with separate release lines, so they can drift apart. Tebako's answer is a declared handoff contract: every runtime release states which handoff semantics it speaks, and the loader checks the declaration before downloading, before mounting, before running anything. A combination that cannot work is refused by name — the error tells you both generations and the remedy — instead of failing later as a corrupted environment or a segfault.
The same rule covers packages and payloads: every artifact belongs to a declared generation, and anything that predates declarations is treated as the oldest one and refused by name when it no longer fits. The failure you meet in practice is a sentence like "this package was pressed by a newer tebako — upgrade your CLI" or "this runtime is too old for this package — pin an older payload or upgrade the runtime": a named error with an exit code, never a silent fallback and never a half-installed cache.
Every failure has a name.
The loader and the runtime's mount driver share a small, stable set of refusal codes. Scripts can branch on them; humans get a one-line message that says what was refused and why.
| Exit | Refusal | What it means |
|---|---|---|
| 65 | manifest | the trailer or a manifest is missing, corrupt, or names no usable entrypoint |
| 66 | launcher ABI | the package speaks a handoff wire format this runtime predates |
| 67 | runtime reference | the runtime reference in the trailer cannot be parsed or honored |
| 69 | unavailable | the runtime cannot be resolved — offline miss, download failure, or a reference to a release that does not exist |
| 70 | checksum | a SHA-256 verification failed; nothing was installed |
| 71 | signature | a signature is invalid — or the package is unsigned and TEBAKO_REQUIRE_SIGNED=1 was set |
| 72 | trust | the signing key is not in the trusted keyring, or key continuity broke |
| 73 | jail | the jail policy is malformed; the run fails closed, nothing mounts |
| 74 | I/O | a filesystem, lock, or install operation failed |
| 75 | contract | the runtime speaks a handoff contract this loader does not — it names both generations and the remedy |
| 76 | install | installation was refused — the publisher froze the package, or the verb belongs to the CLI |
| 77 | era | the artifact comes from a different tebako generation, in either direction |
| 78 | layout | the env image does not match the layout the interpreter was compiled for |
Once the interpreter is running, its own exit codes are the program's — the loader is out of the picture by then.
NEXT · THE STORE
Where everything lives
~/.tebako — content-addressed, SHA-256 anchored, shared machine-wide. Install vs run, locking, offline.
NEXT · TRUST
The chain of trust
Unverified-first with loud warnings, opt-in signing, strict verification of anything signed.
NEXT · EXEC TIERS
Running binaries from images
Preload, wrapped, TFS-native, static — how a toolkit executable runs, and how to choose.