Skip to content

ARCHITECTURE · 03

Concepts.

The pieces every other page in this section builds on: the three-part package, the tpkg trailer that glues it, the two press modes, the machine-wide runtime cache, and the two distinct moments at which a runtime gets resolved.

Note

How to read this section.

Status: shipped — released and downloadable today; every claim links its artifact.

Status: planned — locked design, not yet shipped.

The three-part package.

Note

Status: shipped.

Every tebako package is a composition of up to three independently distributable parts — a bootstrap loader, a language runtime, and one or more filesystem images — glued together by a tpkg trailer at the end of the file. The wire format is normative, and it has exactly one authoritative implementation: crates/tpkg in the product repo — #![forbid(unsafe_code)] Rust, pinned byte-exact against the C99 reference header by golden vectors.

byte 0 → → EOF A · Bootstrap the Rust loader < 3 MB, CI size-gated the process entry point C · Payload slots slot 0: the app image slots 1–7: data, tools mounted at run time B · Runtime slot fat packages only format_id 4 (legacy) installed, never mounted Slot table n × 280 B records offset · size · format_id · mount point Ext blocks type 2: package manifest type 1: v2 signature optional Trailer header 166 B, always at EOF magic · flags · CRC read last, parsed first lean = A + C + trailer · fat = A + B + C + trailer · classic — refused by name: v2 has exactly two package shapes

Figure 1 — Package byte layout: the bootstrap, payload slots, the optional fat runtime slot, the slot table, extension blocks, and the 166-byte trailer header at end of file.

A — the bootstrap.

A small, static Rust binary — under 3 MB, a budget enforced in CI on every platform — that parses the trailer of its own executable, resolves the runtime into the shared cache (cache → embedded fat slot → verified download), and execs it. It carries its own HTTP, TLS, and archive handling: nothing shells out. The tebako-bootstrap crate is the shipping implementation; the C99 repo survives as the behavioral oracle its messages are byte-compared against — it builds nothing you can download. It is published per platform triplet with every tebako release.

B — the language runtime.

Two artifacts, published per (version × platform) by the runtime factory’s releases: the interpreter executable, and an env image — one .tfs holding the standard library, gems, everything the load paths expect. Downloaded once per machine, SHA-256 verified, mounted, never extracted — see runtime as image. Lean packages resolve it on first run; fat packages carry the interpreter as a payload slot and install it offline.

C — the payload slices.

Bare .tfs filesystem images — the application tree, data, native tools — created in-process (no mkdwarfs, no shell-outs) and self-describing through a manifest inside the image at /tpkg/manifest.yaml. Embedded as trailer-described slots, each mounted straight out of the package file at its declared mount point: no extraction, no temp copies.

The tpkg trailer.

Note

Status: shipped — format v1.

The trailer is the byte-level contract of the whole stack, and it has exactly one owner: crates/tpkg. Everything else — the bootstrap, tebako-pkg, the runtime driver — consumes the crate; nothing hand-writes a second copy of the layout. The reader checks magic and CRC, then walks the slot table backwards from EOF. All header integers are little-endian.

The header — 166 bytes, always at EOF.

Offset Bytes Field Meaning

0

10

magic

"TEBAKOTFS\0" — the 4-byte TEBA prefix is what tells absent apart from corrupt

10

4

version

u32, = 1 forever — extensions live in flags, never in version bumps

14

4

package_flags

u32 — bit 0 LEAN · bit 1 SIGNED_V2 · bit 2 NO_INSTALL (table below)

18

4

slot_count

u32, 1–8 (TPKG_MAX_SLOTS)

22

8

slot_table_offset

u64 — absolute file offset of slot record 0

30

128

runtime_ref

resolution hint, e.g. ruby@3.4.2;tebako=0.16.9;image — fat appends ;sha256=<64 hex>

158

4

launcher_abi

u32 — the bootstrap→runtime wire version the package requires (currently 1)

162

4

header_crc32

CRC32 over header bytes [0, 162) — accident integrity, never authenticity

package_flags — three bits, all pass-through.

Bit Flag Meaning

bit 0

TPKG_FLAG_LEAN

the runtime is named by reference (runtime_ref) and resolved at run time; unset means the package carries it as a slot (fat)

bit 1

TPKG_FLAG_SIGNED_V2

the v2 chain-of-trust extension is carried between the slot table and the header. v1-era readers pass the bit through and run the package — trust enforcement is a reader capability, not a format barrier

bit 2

TPKG_FLAG_NO_INSTALL

publisher-frozen: the package RUNS standalone, but every install attempt is refused by name (exit 76). Absence means installable-on-request

The version field stays 1 forever: new capability arrives through flags and extension blocks, so v1-era readers never break on v2 packages.

Each slot record — 280 bytes.

Offset Bytes Field Meaning

0

8

offset

u64 — absolute file offset of the image bytes

8

8

size

u64 — image length in bytes

16

4

format_id

how to read the bytes: 0 auto · 1 dwarfs · 2 squashfs · 3 zip · 5 limnifs — nothing more (see below)

20

4

flags

u32, 0 today — the future home of slot role bits

24

256

mount_point

UTF-8, NUL-padded — where the driver mounts the image

format_id answers exactly one question.

Id Format Meaning

0

auto

detect from the image magic

1

dwarfs

dwarfs-t — the FlatBuffers-metadata format; read-only at runtime, creation-time Writer

2

squashfs

SquashFS images, read as-is

3

zip

zip archives, read as-is

4

runtime

LEGACY ROLE WART — marks the fat runtime slot so v1-era loaders skip handing it over as a mount. Kept for backward compatibility only; never use it for new code

5

limnifs

LimniFS — pure-Rust, content-addressed; the default format for tfs mkimage and tebako press since v0.2.0

Note

Stated plainly: how do I read these bytes is the only thing a format id may say. Whether a slot is a runtime, and which payload carries the entrypoint, are roles — declared by manifests, never encoded in the format axis. format_id 4 is the one historical violation, kept so old loaders keep working; ids ≥ 6 are unallocated, and new semantics must never again be smuggled into the format field. That is the orthogonality law, and it is why a runtime image and an app image can share one container format.

Absent vs corrupt — named errors, never partial behavior.

Error Meaning

NoTrailer

no TEBA prefix in the last 166 bytes — absent, not corrupt (a classic-era bundle; callers may fall back to offset auto-detection)

Magic

prefix present but the full magic mismatches — the trailer is corrupt

Crc

magic valid, header CRC32 bad — the trailer is corrupt

Version

version field ≠ 1

Slots

slot_count outside 1–8

Bounds

slot table outside file bounds

Invalid

structural validation failed (overflowing offset+size, unterminated fixed fields, format_id > 5, …)

The TpkgError set is 1:1 with the C reference’s TPKG_ERR_* codes, and the strerror strings are byte-identical — the golden parity rule applies to the error surface too.

Extension blocks: where v2 lives.

Note

Status: shipped.

Between the slot table and the trailer header sits a chain of typed extension blocks — invisible to v1-era readers, which only ever read the header at EOF and the slot table at its recorded offset. Blocks walk forward from the end of the slot table, self-delimiting by type and length; readers skip unknown types and carry them verbatim, while tebako-pkg validate rejects them with a named error.

The extension-block chain
[bootstrap][payload slots][slot table][ext blocks…][v2 signing ext?][header @EOF]

ext block: [u32be type][u32be length][payload]
  type 1 — v2 signing extension (when present, LAST before the header)
  type 2 — package manifest (YAML)

Type 1 — the chain-of-trust extension.

Per-slot SHA-256 digests (8 × 32 B), the signer’s keyid (the low 64 bits of the OpenPGP fingerprint), and a detached OpenPGP signature (≤ 64 KiB). All its numerics are big-endian — the header stays v1-little-endian. The canonical signed bytes are slot table ‖ digests ‖ keyid ‖ header. Flagged by TPKG_FLAG_SIGNED_V2, never by a version bump — see the chain of trust.

Type 2 — the package manifest.

A YAML block that owns composition: package identity; entries[] mapping each command to its slot, entrypoint, and per-entry runtime_ref (suites and multi-runtime packages, past the trailer’s 128-byte single-ref limit); package-level jail: and env:; and mounts[] per slot (exclusive by default, or union with declared precedence). It references slots; payload manifests stay inside the images — no duplicated authority.

Press modes: lean and fat. Only.

Note

Status: shipped.

tebako press produces two package shapes. Payload images default to LimniFS since v0.2.0 (--format dwarfs stays a first-class opt-in); the shape question is only about where the runtime lives.

Lean — the default.

Bootstrap + payload slots + trailer: TPKG_FLAG_LEAN set, runtime_ref filled. The smallest download; the first run resolves the runtime into the shared cache.

Fat.

A lean package that also carries the runtime interpreter as a payload slot (format_id 4), pinned by ;sha256= in the runtime_ref. First run verifies and installs it into the cache — no network for the interpreter. In the image era the env .tfs resolves alongside: usually already cached from any prior tebako run, else one verified fetch (TEBAKO_OFFLINE=1 makes a miss the named exit 69, not a surprise).

Classic — refused.

v1’s shape: runtime and app stitched into one monolithic binary — no bootstrap, no trailer, no shared cache. v2 retires it by design, and the CLI says so by name: tebako press --mode=classic exits with "the 'classic' press mode is a later tebako-rs milestone (use --mode=lean or --mode=fat)". A refusal, never a silent fallback.

The shared runtime cache.

Note

Status: shipped.

Runtimes are installed once per machine, not once per package. The cache root is $TEBAKO_HOME (default ~/.tebako, %LOCALAPPDATA%\tebako on Windows), with entries of the form runtimes/<lang>-<lv>-<ver>-<triplet>/ — e.g. runtimes/ruby-3.4.2-0.16.9-linux-musl-x86_64/. An entry holds immutable artifacts and their markers only: the interpreter (0755), the env .tfs (read-only, 0444), and .sha256 / .origin markers. Installs are atomic (build in tmp/, then rename) under a per-entry lock, so concurrent first runs are safe and a partial install is invisible. Every tebako package on the machine shares the same cache; the store page has the full layout.

Note

The marker is the trust anchor. A .sha256 sidecar means the artifact was verified at install; it is re-verified on re-fetch, never per run — a cached runtime starts at full speed every time. No extracted layout/ tree lives in the cache anymore: when the press side needs the runtime’s files it extracts the image in-process, per press, into the build prefix. TEBAKO_OFFLINE=1 makes resolution cache-or-named-error; TEBAKO_RUNTIME_MIRROR redirects downloads; tebako cache list and cache prune manage the space.

Press-time vs run-time resolution.

Two different actors can fetch a runtime, at two different moments, on two different machines. They are kept deliberately distinct — conflating them is a locked-off design error.

Press-time — the packager’s machine.

tebako press resolves the runtime into the shared cache while packaging: bundler runs under the target runtime, native extensions compile against it, and a fat press embeds the interpreter it resolved. Resolution reads the runtime release’s manifest.json (with SHA256SUMS.txt as fallback); the bootstrap itself comes from local binaries or the product’s own release, verified the same way.

Run-time — the user’s machine.

The bootstrap resolves the runtime when the packaged binary is first run: cache hit → exec; fat slot → verify against ;sha256= + install; otherwise download from the runtime release → sha256-verify → atomic install. Contract negotiation is fail-closed before any checksum is accepted — a runtime that speaks an unsupported handoff contract is exit 75, naming both generations and the remedy, never a half-installed cache.

The launcher ABI: handoff by reference.

Note

Status: shipped — v1.

The bootstrap never mounts anything — it hands the payload slots to the runtime by reference and execs it; a successful handoff never returns. The wire:

The handoff wire
<runtime> --tebako-image <self|image-path>:<slot|->:<mount> … \
    --tebako-entry <argv0> <user args…>

One --tebako-image triple per payload slot to mount; runtime-role slots are never handed over. Parsers split on the last two colons, so Windows drive letters in paths stay safe, and a malformed triple is a named exit 65. TEBAKO_RUNTIME_IMAGE names the env image, mounted whole at the runtime root (see runtime as image). Two version gates keep the ends honest: the trailer’s launcher_abi field guards the wire format (mismatch → exit 66); the runtime’s declared contract_version guards the semantics (fail-closed negotiation → exit 75).

Go deeper.

How a package works

The concepts on this page in motion: boot sequence, mount model, and every named exit code.

The runtime, as an image

Why part B is two artifacts, and how the env image and your payloads co-mount in one process.

Where everything lives

~/.tebako in full — runtimes, payloads, shims, registries, keys, and the house rules.